Download
76 lines 3 KiBJavaScriptModified 2026-10-16 raw
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
// node --test test/
'use strict';
const test = require('node:test');
const assert = require('node:assert/strict');
const search = require('../internal/render/static/search.js');

const files = [
  { p: 'src/main.c', l: ['#include <stdio.h>', 'int main(void) {', '  printf("Hello World");', '  return 0;', '}'] },
  { p: 'README.md', l: ['# hello', 'say hello to the world', 'HELLO again'] },
  { p: 'lib/helper.lua', l: ['local hello_world = 1', 'return hello_world'] },
];

test('literal, case-insensitive by default', () => {
  const r = search.searchSync(files, 'hello', {}, 100);
  assert.equal(r.total, 6);
  assert.deepEqual(r.files.map((f) => f.p), ['src/main.c', 'README.md', 'lib/helper.lua']);
  const m = r.files[1].matches[2];
  assert.equal(m.n, 3);
  assert.equal(m.text.slice(m.s, m.e), 'HELLO');
});

test('case-sensitive literal', () => {
  const r = search.searchSync(files, 'hello', { caseSensitive: true }, 100);
  assert.equal(r.total, 4);
  assert.equal(search.searchSync(files, 'HELLO', { caseSensitive: true }, 100).total, 1);
});

test('whole word', () => {
  const r = search.searchSync(files, 'hello', { word: true }, 100);
  // "hello_world" and "Hello World"? word chars include _, so helper.lua is excluded;
  // "Hello World" matches (space after).
  assert.deepEqual(r.files.map((f) => f.p), ['src/main.c', 'README.md']);
  assert.equal(r.total, 4);
});

test('regex with line anchors', () => {
  const r = search.searchSync(files, '^\\s*return', { regex: true }, 100);
  assert.equal(r.total, 2);
  assert.throws(() => search.compile('(', { regex: true }));
});

test('path glob filter', () => {
  assert.equal(search.searchSync(files, 'hello', { path: '*.lua' }, 100).total, 2);
  assert.equal(search.searchSync(files, 'hello', { path: 'src/*' }, 100).total, 1);
  assert.equal(search.searchSync(files, 'hello', { path: '**/*.c' }, 100).total, 1);
  assert.equal(search.searchSync(files, 'hello', { path: 'nope/*' }, 100).total, 0);
  assert.ok(search.globToRegExp('a?c').test('abc'));
  assert.ok(!search.globToRegExp('a?c').test('a/c'));
});

test('result cap and truncation flag', () => {
  const r = search.searchSync(files, 'hello', {}, 3);
  assert.equal(r.total, 3);
  assert.ok(r.truncated);
  assert.equal(r.files.reduce((n, f) => n + f.matches.length, 0), 3);
});

test('empty query matches nothing', () => {
  assert.equal(search.searchSync(files, '', {}, 10).total, 0);
  assert.equal(search.compile('', {}), null);
});

test('regex special characters are literal by default', () => {
  assert.equal(search.searchSync(files, 'printf(', {}, 10).total, 1);
  assert.equal(search.searchSync(files, '.', { caseSensitive: true }, 100).total, 1); // only "stdio.h"
  assert.equal(search.searchSync(files, '.', { regex: true }, 100).total, 10); // every line
});

test('iterSearch can be driven incrementally (cancellation point)', () => {
  const m = search.compile('hello', {});
  const it = search.iterSearch(files, m, null, []);
  const first = it.next();
  assert.equal(first.done, false);
  assert.equal(first.value.p, 'src/main.c');
  it.return();
});