mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2026-10-25 15:05:25 +00:00
142 lines
5.1 KiB
TypeScript
142 lines
5.1 KiB
TypeScript
/**
|
|
* ToolchainSource tests - dumb full-text search over the worker's bundled
|
|
* header files. The worker is mocked by providing a queryWorker on the
|
|
* stubbed project (same provider injection as ProjectSource).
|
|
*/
|
|
|
|
import * as assert from 'assert';
|
|
import { ToolchainSource } from '../../src/ide/search/toolchainsource';
|
|
import { setProjectProvider } from '../../src/ide/search/projectsource';
|
|
|
|
/** Build a fake "worker" backed by a hash of paths -> text. */
|
|
function makeFs(files: { [path: string]: string }) {
|
|
const paths = Object.keys(files).sort();
|
|
return {
|
|
mainPath: 'main.c',
|
|
platform_id: 'nes',
|
|
iterateFiles: (cb: any) => {},
|
|
getToolForFilename: () => 'cc65',
|
|
queryWorker: async (msg: any) => {
|
|
if (msg.listshared != null) {
|
|
const prefix = msg.listshared + '/';
|
|
return { output: paths.filter(p => p.startsWith(prefix)) };
|
|
}
|
|
if (msg.readshared != null) {
|
|
const text = files[msg.readshared];
|
|
if (text == null) return { output: null };
|
|
return { output: new TextEncoder().encode(text) };
|
|
}
|
|
return null;
|
|
}
|
|
};
|
|
}
|
|
|
|
const NES_H = `#ifndef NES_H
|
|
#define NES_H
|
|
/* NES registers */
|
|
#define PPU_CTRL 0x2000
|
|
#define PPU_MASK 0x2001
|
|
/* Turn off the screen */
|
|
#define VBLANK 0x00
|
|
#define PPU_STATUS 0x2002
|
|
static char PPU_READ(unsigned char a) {
|
|
return a;
|
|
}
|
|
#endif
|
|
`;
|
|
|
|
const APPLE_H = `#ifndef APPLE_H
|
|
#define APPLE_H
|
|
/* Apple II registers */
|
|
#define KBD 0xC000
|
|
/* Read keyboard */
|
|
#define KBD_READ 0xC000
|
|
#endif
|
|
`;
|
|
|
|
describe('ToolchainSource', function () {
|
|
after(function () {
|
|
setProjectProvider(() => null);
|
|
});
|
|
|
|
it('should list and lazily search bundled header files', async function () {
|
|
setProjectProvider(() => makeFs({ '/share/cc65/include/nes.h': NES_H, '/share/cc65/include/apple.h': APPLE_H }));
|
|
const ts = new ToolchainSource();
|
|
|
|
// First query triggers ready(), which lists and reads every header file
|
|
const hits = await ts.query('VBLANK', 10);
|
|
|
|
assert.ok(hits.length >= 1, 'should find VBLANK in nes.h');
|
|
const rec = hits[0].record;
|
|
assert.strictEqual(rec.kind, 'text');
|
|
assert.strictEqual(rec.source, 'toolchain');
|
|
assert.strictEqual(rec.file, '/share/cc65/include/nes.h');
|
|
assert.ok(rec.line && rec.line > 0);
|
|
// The two hash-flavored hits should both appear (each file is searched)
|
|
assert.ok(hits.some(h => h.record.file === '/share/cc65/include/nes.h' && h.record.name.includes('VBLANK')));
|
|
});
|
|
|
|
it('should be case-insensitive', async function () {
|
|
setProjectProvider(() => makeFs({ '/share/cc65/include/nes.h': NES_H }));
|
|
const ts = new ToolchainSource();
|
|
const hits = await ts.query('vblank', 10);
|
|
assert.ok(hits.length >= 1);
|
|
});
|
|
|
|
it('should require at least 2 chars', async function () {
|
|
setProjectProvider(() => makeFs({ '/share/cc65/include/nes.h': NES_H }));
|
|
const ts = new ToolchainSource();
|
|
const hits = await ts.query('x', 10);
|
|
assert.strictEqual(hits.length, 0);
|
|
});
|
|
|
|
it('should read every header before answering the first query', async function () {
|
|
const fs = makeFs({
|
|
'/share/cc65/include/a.h': '#define AAA 1\n',
|
|
'/share/cc65/include/b.h': '#define BBB 2\n',
|
|
'/share/cc65/include/tgi.h': '#define TGI_OK 1\n',
|
|
});
|
|
setProjectProvider(() => fs);
|
|
const ts = new ToolchainSource();
|
|
// Force a tiny read batch so the last file sorts past the first batch
|
|
(ts as any).batchSize = 1;
|
|
|
|
// The first query must find a match in a header that sorts last;
|
|
// otherwise results depend on how many times the user types.
|
|
const hits = await ts.query('TGI', 10);
|
|
assert.ok(hits.length >= 1, 'should index all headers on the first query');
|
|
assert.strictEqual(hits[0].record.file, '/share/cc65/include/tgi.h');
|
|
assert.strictEqual(ts['loaded'].size, 3);
|
|
});
|
|
|
|
it('should rank matches near the start of the line higher', async function () {
|
|
setProjectProvider(() => makeFs({ '/share/cc65/include/nes.h': NES_H }));
|
|
const ts = new ToolchainSource();
|
|
const hits = await ts.query('PPU', 10);
|
|
assert.ok(hits.length >= 2, 'PPU matches several lines');
|
|
// Earliest-column hit first (PPU_CTRL at col 17 is #define PPU_CTRL); the
|
|
// column-based score should order them without any flakiness
|
|
const cols = hits.map(h => h.ranges ? h.ranges[0] : 0);
|
|
for (let i = 1; i < cols.length; i++) {
|
|
assert.ok(cols[i - 1] <= cols[i], 'hits sorted by column');
|
|
}
|
|
});
|
|
|
|
it('should skip non-searchable file types', async function () {
|
|
const ts = new ToolchainSource();
|
|
assert.strictEqual((ts as any).isSearchable('/share/cc65/include/nes.h'), true);
|
|
assert.strictEqual((ts as any).isSearchable('/share/cc65/include/nes.o'), false);
|
|
assert.strictEqual((ts as any).isSearchable('/lib/binary.bin'), false);
|
|
assert.strictEqual((ts as any).isSearchable('/scripts/makefile'), true); // no ext
|
|
});
|
|
|
|
it('should handle missing worker gracefully', async function () {
|
|
setProjectProvider(() => ({
|
|
mainPath: 'main.c',
|
|
iterateFiles: (cb: any) => {},
|
|
}));
|
|
const ts = new ToolchainSource();
|
|
const hits = await ts.query('anything', 10);
|
|
assert.deepStrictEqual(hits, []);
|
|
});
|
|
}); |