mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
185 lines
6.0 KiB
JavaScript
185 lines
6.0 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { execFileSync } from 'node:child_process';
|
|
import {
|
|
mkdtempSync,
|
|
mkdirSync,
|
|
writeFileSync,
|
|
rmSync,
|
|
symlinkSync,
|
|
} from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { analyzeModule } from '../../scripts/module-analysis.mjs';
|
|
import { checkImportDirections } from '../../scripts/check-import-directions.mjs';
|
|
|
|
function fixture(t, exports = {}) {
|
|
const root = mkdtempSync(path.join(os.tmpdir(), 'gev-directions-'));
|
|
t.after(() => rmSync(root, { recursive: true, force: true }));
|
|
execFileSync('git', ['init', '--quiet'], { cwd: root });
|
|
const write = (name, source = '') => {
|
|
mkdirSync(path.dirname(path.join(root, name)), { recursive: true });
|
|
writeFileSync(path.join(root, name), source);
|
|
};
|
|
write(
|
|
'package.json',
|
|
JSON.stringify({ name: '@gods-eye-view/core', exports }),
|
|
);
|
|
write('src/data/feedState.js');
|
|
return { root, write };
|
|
}
|
|
|
|
test('module analysis parses dependencies and platform references without reading comments or property labels', () => {
|
|
assert.deepEqual(
|
|
analyzeModule(
|
|
"// import('node:fs'); document\nimport x from 'one'; export { x } from 'two'; export * from 'three'; import('four'); const data = { document: 1 }; data.window;",
|
|
),
|
|
{ imports: ['one', 'two', 'three', 'four'], browser: [] },
|
|
);
|
|
assert.deepEqual(
|
|
analyzeModule(
|
|
"const data = { element: document }; globalThis['navigator'];",
|
|
).browser,
|
|
['document', 'navigator'],
|
|
);
|
|
assert.throws(() => analyzeModule('import(name)'), /Computed/);
|
|
assert.throws(() => analyzeModule("require('node:fs')"), /ES imports/);
|
|
});
|
|
|
|
test('portable sources can share records, and standalone can assemble rendering', (t) => {
|
|
const { root, write } = fixture(t, {
|
|
'./layers/demo/source': './src/layers/demo/source.js',
|
|
});
|
|
write('src/layers/demo/source.js', "export { read } from './records.js';");
|
|
write('src/layers/demo/records.js', 'export const read = (value) => value;');
|
|
write('src/standalone/catalog.js', "import '../layers/demo/index.js';");
|
|
write('src/layers/demo/index.js', "import * as Cesium from 'cesium';");
|
|
assert.equal(checkImportDirections(root).modules, 5);
|
|
});
|
|
|
|
for (const [name, files, pattern] of [
|
|
[
|
|
'unreachable source imports rendering',
|
|
{
|
|
'src/layers/demo/source.js': "import './rendering.js';",
|
|
'src/layers/demo/rendering.js': '',
|
|
},
|
|
/Source imports rendering/,
|
|
],
|
|
[
|
|
'source reaches a platform through a record helper',
|
|
{
|
|
'src/layers/demo/source.js': "import './records.js';",
|
|
'src/layers/demo/records.js': "import 'cesium';",
|
|
},
|
|
/reaches platform\/rendering/,
|
|
],
|
|
[
|
|
'source reaches a DOM helper',
|
|
{
|
|
'src/sources/demo.js': "import '../data/helper.js';",
|
|
'src/data/helper.js': 'export const view = () => globalThis.document;',
|
|
},
|
|
/reaches browser globals/,
|
|
],
|
|
[
|
|
'browser imports Node without prefix',
|
|
{ 'src/unused.mjs': "import 'fs/promises';" },
|
|
/Node builtin/,
|
|
],
|
|
[
|
|
'browser hides Node outside src',
|
|
{
|
|
'src/unused.js': "import '../shared/helper.js';",
|
|
'shared/helper.js': "export * from 'node:fs';",
|
|
},
|
|
/Browser graph reaches Node/,
|
|
],
|
|
[
|
|
'browser imports server',
|
|
{
|
|
'src/data/demo.js': "import '../../server/helper.js';",
|
|
'server/helper.js': '',
|
|
},
|
|
/Browser imports server/,
|
|
],
|
|
[
|
|
'reusable module selects standalone',
|
|
{
|
|
'src/app/demo.js': "import '../standalone/catalog.js';",
|
|
'src/standalone/catalog.js': '',
|
|
},
|
|
/Reusable module imports standalone/,
|
|
],
|
|
[
|
|
'provider selects application setup',
|
|
{
|
|
'server/providers/demo.js': "import '../standalone/key-setup.js';",
|
|
'server/standalone/key-setup.js': '',
|
|
},
|
|
/Provider imports application/,
|
|
],
|
|
[
|
|
'voice controls reach protocol indirectly',
|
|
{
|
|
'src/voice/sessionCommands.js': "import './helper.js';",
|
|
'src/voice/helper.js': "import './realtimeSession.js';",
|
|
'src/voice/realtimeSession.js': '',
|
|
},
|
|
/Common voice controls import protocol/,
|
|
],
|
|
[
|
|
'dynamic import cannot evade direction',
|
|
{ 'src/app/demo.js': "const target = './module.js'; import(target);" },
|
|
/Computed/,
|
|
],
|
|
[
|
|
'browser imports test helper',
|
|
{
|
|
'src/app/demo.js': "import '../testSupport/helper.js';",
|
|
'src/testSupport/helper.js': '',
|
|
},
|
|
/non-runtime owner/,
|
|
],
|
|
]) {
|
|
test(`rejects ${name}`, (t) => {
|
|
const { root, write } = fixture(t);
|
|
for (const [file, body] of Object.entries(files)) write(file, body);
|
|
assert.throws(() => checkImportDirections(root), pattern);
|
|
});
|
|
}
|
|
|
|
test('self package imports cannot evade portable ownership', (t) => {
|
|
const { root, write } = fixture(t, { './view': './src/ui/view.js' });
|
|
write('src/sources/demo.js', "import '@gods-eye-view/core/view';");
|
|
write('src/ui/view.js');
|
|
assert.throws(() => checkImportDirections(root), /Source imports rendering/);
|
|
});
|
|
|
|
test('compatibility exceptions are limited to the two existing composition entries', (t) => {
|
|
const { root, write } = fixture(t);
|
|
write('src/ui.js', "import './standalone/catalog.js';");
|
|
write('src/standalone/catalog.js');
|
|
write('server/providers/local.js', "import '../standalone/key-setup.js';");
|
|
write('server/standalone/key-setup.js');
|
|
assert.doesNotThrow(() => checkImportDirections(root));
|
|
write('src/ui/new.js', "import '../standalone/catalog.js';");
|
|
assert.throws(
|
|
() => checkImportDirections(root),
|
|
/Reusable module imports standalone/,
|
|
);
|
|
});
|
|
|
|
test('symlink aliases do not hide renderer ownership', (t) => {
|
|
const { root, write } = fixture(t);
|
|
write('src/sources/demo.js', "import './helper/view.js';");
|
|
write('src/ui/view.js');
|
|
// Directory junctions exercise the same resolved edge without Windows symlink privileges.
|
|
symlinkSync(
|
|
path.join(root, 'src/ui'),
|
|
path.join(root, 'src/sources/helper'),
|
|
'junction',
|
|
);
|
|
assert.throws(() => checkImportDirections(root), /rendering|symlinks/);
|
|
});
|