更新
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readdirSync, readFileSync } from "node:fs";
|
||||
import { join, relative } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import test from "node:test";
|
||||
import { compileScript, compileTemplate, parse } from "vue/compiler-sfc";
|
||||
import ts from "typescript";
|
||||
|
||||
const root = fileURLToPath(new URL("../src/", import.meta.url));
|
||||
const nativeControls = new Set([
|
||||
"switch", "slider", "picker", "picker-view", "checkbox", "checkbox-group",
|
||||
"radio", "radio-group",
|
||||
]);
|
||||
|
||||
function vueFiles(directory) {
|
||||
return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
|
||||
const path = join(directory, entry.name);
|
||||
return entry.isDirectory() ? vueFiles(path) : path.endsWith(".vue") ? [path] : [];
|
||||
});
|
||||
}
|
||||
|
||||
function compile(source, filename) {
|
||||
return compileTemplate({
|
||||
source, filename, id: "native-component-regression",
|
||||
compilerOptions: {
|
||||
isNativeTag: (tag) => nativeControls.has(tag) || ["view", "text", "input", "textarea"].includes(tag),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function elements(node) {
|
||||
return [node, ...(node.children ?? []).flatMap(elements)].filter((item) => item.type === 1);
|
||||
}
|
||||
|
||||
test("the check detects unsupported v-model on native switches", () => {
|
||||
const result = compile('<switch v-model="enabled" />', "invalid-switch.vue");
|
||||
assert.ok(result.errors.some((error) => String(error.message).includes("v-model can only be used")));
|
||||
});
|
||||
|
||||
test("all mobile templates compile without unsupported native v-model", () => {
|
||||
const files = vueFiles(root);
|
||||
assert.ok(files.length > 0);
|
||||
for (const filename of files) {
|
||||
const { descriptor, errors } = parse(readFileSync(filename, "utf8"), { filename });
|
||||
assert.deepEqual(errors, [], relative(root, filename));
|
||||
if (!descriptor.template) continue;
|
||||
const result = compile(descriptor.template.content, filename);
|
||||
assert.deepEqual(result.errors, [], relative(root, filename));
|
||||
}
|
||||
});
|
||||
|
||||
for (const [page, model] of [["settings", "privacy"], ["notification-settings", "form"]]) {
|
||||
test(`${page} switch change updates both checked and unchecked form values`, () => {
|
||||
const filename = join(root, "pages", page, "index.vue");
|
||||
const { descriptor } = parse(readFileSync(filename, "utf8"), { filename });
|
||||
const switches = elements(descriptor.template.ast).filter((node) => node.tag === "switch");
|
||||
assert.equal(switches.length, 1);
|
||||
const props = switches[0].props;
|
||||
assert.ok(!props.some((prop) => prop.name === "model"));
|
||||
const checked = props.find((prop) => prop.name === "bind" && prop.arg?.content === "checked");
|
||||
const change = props.find((prop) => prop.name === "on" && prop.arg?.content === "change");
|
||||
assert.ok(checked?.exp?.content);
|
||||
assert.ok(change?.exp?.content);
|
||||
const state = { enabled: false, otherSetting: true };
|
||||
// Exercise the actual event handler and bindings without mounting a native
|
||||
// runtime or calling the API; only the component's own handler is evaluated.
|
||||
const script = compileScript(descriptor, { id: `test-${page}` });
|
||||
const handler = script.scriptSetupAst.find((node) => node.type === "FunctionDeclaration" && node.id.name === "setSwitch");
|
||||
assert.ok(handler);
|
||||
const handlerSource = descriptor.scriptSetup.content.slice(handler.start, handler.end);
|
||||
const handlerJS = ts.transpileModule(handlerSource, {
|
||||
compilerOptions: { target: ts.ScriptTarget.ES2020 },
|
||||
}).outputText;
|
||||
const setSwitch = new Function(model, `${handlerJS}; return setSwitch;`)({ value: state });
|
||||
const update = new Function("setSwitch", "item", "$event", change.exp.content);
|
||||
const readChecked = new Function(model, "item", `return ${checked.exp.content}`);
|
||||
for (const value of [true, false, true]) {
|
||||
update(setSwitch, ["enabled"], { detail: { value } });
|
||||
assert.equal(state.enabled, value);
|
||||
assert.equal(readChecked(state, ["enabled"]), value);
|
||||
assert.equal(state.otherSetting, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user