134 lines
5.2 KiB
JavaScript
134 lines
5.2 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 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'",
|
|
);
|
|
});
|