Files
WRNexusJS/editors/vscode/test/grammar-apis.test.js
T
2026-08-20 12:31:19 +05:30

137 lines
5.3 KiB
JavaScript

"use strict";
const assert = require("node:assert");
const { test } = require("node:test");
const { readFileSync } = require("node:fs");
const { join } = require("node:path");
const grammarPath = join(__dirname, "../syntaxes/wrn.tmLanguage.json");
const grammar = JSON.parse(readFileSync(grammarPath, "utf8"));
test("the grammar knows the apis block", () => {
assert.ok(grammar.repository["apis-block"], "apis should appear as a block keyword");
});
test("client keeps its highlighting where it is still valid", () => {
// client state {}, runtime = "client", and client function all survive.
// Only the client {} data block was removed.
assert.match(JSON.stringify(grammar), /client/, "client must still be matched");
assert.match(
JSON.stringify(grammar),
/shared/,
"the shared function modifier must still be matched",
);
});
// --- Structural assertions: these fail if the rule they name is deleted, ---
// --- even though the substrings "apis"/"client" remain elsewhere in the ---
// --- file (e.g. inside unrelated pattern names or comments). ---
/**
* vscode-textmate is not a dependency of this repo (confirmed via
* `require.resolve`), so real tokenization is unavailable here. Instead we
* pull each rule's own `begin`/`match` regex out of the parsed grammar
* object and exercise it directly with the JS regex engine. The grammar's
* patterns use only `\b`, character classes, and `(?<=...)` lookbehind --
* all supported by native JS regexes -- so this is a faithful proxy for
* "does this specific rule match this specific source line" without
* needing an Oniguruma-backed tokenizer.
*/
function ruleByTopLevelBlockName(name) {
const entry = grammar.repository[name];
assert.ok(entry, `repository.${name} should exist`);
return entry;
}
function topLevelBlockIncludes(name) {
return grammar.repository.blocks.patterns.some((p) => p.include === `#${name}`);
}
test("blocks includes the apis-block pattern", () => {
assert.ok(topLevelBlockIncludes("apis-block"), "#blocks must include #apis-block");
});
test("the apis-block begin pattern matches the container keyword", () => {
const rule = ruleByTopLevelBlockName("apis-block");
const beginRe = new RegExp(rule.begin);
assert.ok(beginRe.test("apis {"), "apis-block begin should match 'apis {'");
assert.ok(!beginRe.test("ssr {"), "apis-block begin must not match 'ssr {'");
assert.ok(!beginRe.test("client {"), "apis-block begin must not match 'client {'");
});
test("the apis-block declares an entry pattern for <name> <METHOD> <path>", () => {
const rule = ruleByTopLevelBlockName("apis-block");
const entryPattern = (rule.patterns || []).find(
(p) => p.name === "apis-entry" || (p.begin && /GET\|POST/.test(p.begin)),
);
assert.ok(entryPattern, "apis-block should contain an entry declaration pattern");
const entryRe = new RegExp(entryPattern.begin || entryPattern.match);
assert.ok(
entryRe.test("searchUsers POST /api/users {"),
"the entry pattern should match '<name> <METHOD> <path> {'",
);
});
test("the ssr/client data-block pattern (mode-block) is gone", () => {
assert.strictEqual(
grammar.repository["mode-block"],
undefined,
"mode-block (the removed ssr {}/client {} data-block rule) must be deleted from the repository",
);
assert.ok(
!topLevelBlockIncludes("mode-block"),
"#blocks must no longer include the removed #mode-block pattern",
);
});
// --- Regression coverage: constructs that must NOT be broken by this change ---
test("REGRESSION: client state {} still highlights via grouped-state-block", () => {
const rule = ruleByTopLevelBlockName("grouped-state-block");
const beginRe = new RegExp(rule.begin);
assert.ok(
beginRe.test("client state {"),
"grouped-state-block must still match 'client state {'",
);
});
test('REGRESSION: runtime = "client" still highlights via execution-decl', () => {
const rule = ruleByTopLevelBlockName("execution-decl");
const beginRe = new RegExp(rule.begin);
assert.ok(
beginRe.test('runtime = "client"'),
"execution-decl must still match 'runtime = \"client\"'",
);
});
test("REGRESSION: the client function modifier still highlights via functions-block", () => {
const rule = ruleByTopLevelBlockName("functions-block");
const modifierPattern = rule.patterns.find(
(p) => (p.match && /storage\.modifier\.async/.test("")) || p.match,
);
const matchRe = new RegExp(
rule.patterns.find(
(p) => p.captures && p.captures["1"]?.name === "storage.modifier.runtime.wrn",
).match,
);
assert.ok(
matchRe.test("client async function go(): Promise<void> {"),
"functions-block must still recognize the 'client' runtime modifier on a function",
);
});
test("REGRESSION: the client function modifier also highlights via v060-keywords", () => {
const rule = ruleByTopLevelBlockName("v060-keywords");
const modifierPattern = rule.patterns.find((p) => p.name === "storage.modifier.runtime.wrn");
assert.ok(modifierPattern, "v060-keywords should have a storage.modifier.runtime.wrn rule");
const matchRe = new RegExp(modifierPattern.match);
assert.ok(
matchRe.test("client function go"),
"v060-keywords must still recognize 'client' before 'function'",
);
assert.ok(
matchRe.test("client state"),
"v060-keywords must still recognize 'client' before 'state'",
);
});