AGPL-3.0-only, a source link in the app, and everything on dev since #5 #6
@@ -143,8 +143,12 @@ jobs:
|
||||
echo "build=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
# The web's whole verification: the type check, then its unit tests. Both in the
|
||||
# job `build` already needs, so a red web test blocks the image like any other
|
||||
# lane (rule 177). The unit tests arrived with #5166; before them the web copy of
|
||||
# the note grammar, mirrored in Rust and Python, was guarded by nothing.
|
||||
typecheck:
|
||||
name: TypeScript typecheck
|
||||
name: Web typecheck and unit tests
|
||||
if: github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main'
|
||||
runs-on: python-ci
|
||||
container:
|
||||
@@ -160,6 +164,10 @@ jobs:
|
||||
run: npx vue-tsc --noEmit
|
||||
working-directory: frontend
|
||||
|
||||
- name: Unit tests
|
||||
run: npm test
|
||||
working-directory: frontend
|
||||
|
||||
lint:
|
||||
name: Python lint
|
||||
if: github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main'
|
||||
|
||||
@@ -89,7 +89,7 @@ jobs:
|
||||
# bundling. No `always()` or `continue-on-error` anywhere on this path: a skipped
|
||||
# or failed verify must leave both publishers skipped.
|
||||
verify:
|
||||
name: Clippy, tests and rustfmt
|
||||
name: Web tests, clippy, Rust tests and rustfmt
|
||||
needs: [decide]
|
||||
if: needs.decide.outputs.build == 'true'
|
||||
runs-on: python-ci
|
||||
@@ -105,6 +105,13 @@ jobs:
|
||||
run: npm ci && npm run build
|
||||
working-directory: frontend
|
||||
|
||||
# The web's unit tests, HERE as well as in ci.yml. The installers embed this
|
||||
# frontend, and ci.yml's verdict is invisible to this workflow — run there only,
|
||||
# a red web test would still let both installers publish (rule 177).
|
||||
- name: Web unit tests
|
||||
run: npm test
|
||||
working-directory: frontend
|
||||
|
||||
# --locked on the FIRST cargo invocation is the lockfile gate: it fails the
|
||||
# run if Cargo.toml and the committed Cargo.lock disagree, instead of silently
|
||||
# re-resolving (issue 2102). Both publishing jobs need this job, so neither
|
||||
|
||||
@@ -7,12 +7,10 @@ import org.junit.Test
|
||||
/**
|
||||
* Pins the derived-colour rule against `frontend/src/notes/colors.ts`.
|
||||
*
|
||||
* These are not tests of Kotlin — they are the ONE mechanical guard the mirrored pair
|
||||
* has. The web side is TypeScript with no test runner (its CI lane is `vue-tsc
|
||||
* --noEmit` and nothing else), so if these values drift, nothing on that surface will
|
||||
* say so and a tag will simply be a different colour on the phone than in the browser.
|
||||
* The same names and hashes are written into colors.ts as a comment; changing either
|
||||
* side means changing both and re-checking here.
|
||||
* These are not tests of Kotlin — they pin the pair. The web runs the same values
|
||||
* from `core/testdata/grammar.json` (`tint`) in `notes/grammar.test.ts`; this file
|
||||
* writes them out by hand, so changing the hash or the key order means changing the
|
||||
* fixture and this file together, or one of the two suites goes red.
|
||||
*
|
||||
* SMALLER SINCE M315. Half of what this file used to pin — the generated card fill, and
|
||||
* the resolution order that chose between a picked colour, a tag's and a generated one
|
||||
|
||||
@@ -770,4 +770,78 @@ mod tests {
|
||||
assert_eq!(touched, body);
|
||||
assert_eq!(extract_items(&touched), items);
|
||||
}
|
||||
|
||||
// ── the shared fixture ───────────────────────────────────────────────────
|
||||
//
|
||||
// core/testdata/grammar.json is the one set of cases the server (pytest), the web
|
||||
// (vitest) and this file all run. The cases above stay as this file's own
|
||||
// reasoning; these are the ones the other languages have agreed to.
|
||||
|
||||
fn fixture() -> serde_json::Value {
|
||||
serde_json::from_str(include_str!("../../testdata/grammar.json"))
|
||||
.expect("grammar.json parses")
|
||||
}
|
||||
|
||||
fn strings(v: &serde_json::Value) -> Vec<String> {
|
||||
v.as_array()
|
||||
.expect("an array")
|
||||
.iter()
|
||||
.map(|s| s.as_str().expect("a string").to_string())
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fixture_task_lines() {
|
||||
for case in fixture()["task_lines"].as_array().unwrap() {
|
||||
let line = case["line"].as_str().unwrap();
|
||||
let got: Vec<(String, bool)> = extract_items(line)
|
||||
.into_iter()
|
||||
.map(|i| (i.text, i.checked))
|
||||
.collect();
|
||||
let want: Vec<(String, bool)> = match &case["item"] {
|
||||
serde_json::Value::Null => Vec::new(),
|
||||
item => vec![(
|
||||
item["text"].as_str().unwrap().to_string(),
|
||||
item["checked"].as_bool().unwrap(),
|
||||
)],
|
||||
};
|
||||
assert_eq!(got, want, "line {line:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fixture_rendered_items() {
|
||||
for case in fixture()["rendered_items"].as_array().unwrap() {
|
||||
let text = case["text"].as_str().unwrap();
|
||||
let checked = case["checked"].as_bool().unwrap();
|
||||
assert_eq!(render_item(text, checked), case["line"].as_str().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fixture_tags() {
|
||||
for case in fixture()["tags"].as_array().unwrap() {
|
||||
let body = case["body"].as_str().unwrap();
|
||||
assert_eq!(extract_tags(body), strings(&case["tags"]), "body {body:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fixture_lifts() {
|
||||
for case in fixture()["lifts"].as_array().unwrap() {
|
||||
let body = case["body"].as_str().unwrap();
|
||||
let (standalone, inline, lifted) = lift_standalone_tags(body);
|
||||
assert_eq!(
|
||||
standalone,
|
||||
strings(&case["standalone"]),
|
||||
"standalone, body {body:?}"
|
||||
);
|
||||
assert_eq!(inline, strings(&case["inline"]), "inline, body {body:?}");
|
||||
assert_eq!(
|
||||
lifted,
|
||||
case["lifted"].as_str().unwrap(),
|
||||
"lifted, body {body:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+88
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"_about": [
|
||||
"The cases every implementation of the note grammar is tested against.",
|
||||
"One file, read by four test suites: core (derive.rs), the server (checklist.py, tags.py), the web (markdown.ts, colors.ts) and, for the tint, Android (DerivedTint.kt, which pins the same values by hand).",
|
||||
"A difference between any two implementations is a note that changes shape when it syncs. Add a case here, not to one suite.",
|
||||
"Lives under core/ because core is the definition, and because core/ is in the desktop, Android and server file sets, so changing a case reruns every suite that reads it.",
|
||||
"Tag cases are plain text on purpose. The web's renderer lets `code` and **bold** claim a `#` first, which the derivers do not; that is a rendering choice, not grammar, and is tested where it lives."
|
||||
],
|
||||
|
||||
"task_lines": [
|
||||
{ "line": "- [ ] milk", "item": { "checked": false, "text": "milk" } },
|
||||
{ "line": "- [x] milk", "item": { "checked": true, "text": "milk" } },
|
||||
{ "line": "- [X] milk", "item": { "checked": true, "text": "milk" } },
|
||||
{ "line": "* [ ] star bullet", "item": { "checked": false, "text": "star bullet" } },
|
||||
{ "line": " - [ ] indented", "item": { "checked": false, "text": "indented" } },
|
||||
{ "line": "\t- [ ] tab indent", "item": { "checked": false, "text": "tab indent" } },
|
||||
{ "line": "- [ ] two spaces after the bullet", "item": { "checked": false, "text": "two spaces after the bullet" } },
|
||||
{ "line": "- [ ]", "item": { "checked": false, "text": "" } },
|
||||
{ "line": "- [ ] ", "item": { "checked": false, "text": "" } },
|
||||
{ "line": "- [ ] trailing space kept ", "item": { "checked": false, "text": "trailing space kept " } },
|
||||
{ "line": "- [ ]x", "item": null },
|
||||
{ "line": "-[ ] x", "item": null },
|
||||
{ "line": "+ [ ] plus bullet", "item": null },
|
||||
{ "line": "- [y] x", "item": null },
|
||||
{ "line": "- [ ] x", "item": null },
|
||||
{ "line": "text - [ ] x", "item": null },
|
||||
{ "line": "- milk", "item": null },
|
||||
{ "line": "", "item": null }
|
||||
],
|
||||
|
||||
"rendered_items": [
|
||||
{ "text": "milk", "checked": false, "line": "- [ ] milk" },
|
||||
{ "text": "milk", "checked": true, "line": "- [x] milk" },
|
||||
{ "text": "", "checked": false, "line": "- [ ]" },
|
||||
{ "text": "", "checked": true, "line": "- [x]" }
|
||||
],
|
||||
|
||||
"tags": [
|
||||
{ "body": "#todo buy milk", "tags": ["todo"] },
|
||||
{ "body": "buy #milk and #eggs", "tags": ["milk", "eggs"] },
|
||||
{ "body": "#Todo and #todo", "tags": ["Todo"] },
|
||||
{ "body": "line one\n#two", "tags": ["two"] },
|
||||
{ "body": "#tag, and #more.", "tags": ["tag", "more"] },
|
||||
{ "body": "#x-y_z9", "tags": ["x-y_z9"] },
|
||||
{ "body": "#café #über", "tags": ["café", "über"] },
|
||||
{ "body": "(#todo)", "tags": ["todo"] },
|
||||
{ "body": "end.#tag", "tags": ["tag"] },
|
||||
{ "body": "#tag#more", "tags": ["tag"] },
|
||||
{ "body": "a#b", "tags": [] },
|
||||
{ "body": "x-#tag", "tags": [] },
|
||||
{ "body": "##x", "tags": [] },
|
||||
{ "body": "#1st", "tags": [] },
|
||||
{ "body": "#2024", "tags": [] },
|
||||
{ "body": "#_x", "tags": [] },
|
||||
{ "body": "# heading", "tags": [] },
|
||||
{ "body": "see https://x.com/page#frag", "tags": [] },
|
||||
{ "body": "", "tags": [] }
|
||||
],
|
||||
|
||||
"lifts": [
|
||||
{ "body": "#todo\nreorganize the homepage", "standalone": ["todo"], "inline": [], "lifted": "reorganize the homepage" },
|
||||
{ "body": "buy milk #grocery", "standalone": [], "inline": ["grocery"], "lifted": "buy milk #grocery" },
|
||||
{ "body": "first\n\n#todo\n\nsecond", "standalone": ["todo"], "inline": [], "lifted": "first\n\nsecond" },
|
||||
{ "body": "#todo\ncall #todo later", "standalone": [], "inline": ["todo"], "lifted": "call #todo later" },
|
||||
{ "body": "#a #b", "standalone": [], "inline": ["a", "b"], "lifted": "#a #b" },
|
||||
{ "body": "```\n# comment #todo\n```", "standalone": [], "inline": ["todo"], "lifted": "```\n# comment #todo\n```" },
|
||||
{ "body": "(#todo)\nnotes", "standalone": [], "inline": ["todo"], "lifted": "(#todo)\nnotes" }
|
||||
],
|
||||
|
||||
"tint": {
|
||||
"keys": ["red", "orange", "yellow", "green", "teal", "blue", "purple", "pink", "gray"],
|
||||
"hashes": [
|
||||
{ "id": "00000000-0000-0000-0000-000000000000", "hash": 3192360657, "color": "purple" },
|
||||
{ "id": "11111111-1111-1111-1111-111111111111", "hash": 1031130113, "color": "blue" },
|
||||
{ "id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "hash": 4043892016, "color": "orange" },
|
||||
{ "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "hash": 1533351232, "color": "orange" }
|
||||
],
|
||||
"names": [
|
||||
{ "name": "todo", "color": "pink" },
|
||||
{ "name": "grocery", "color": "blue" },
|
||||
{ "name": "work", "color": "green" },
|
||||
{ "name": "home", "color": "gray" },
|
||||
{ "name": "ideas", "color": "green" },
|
||||
{ "name": "reading", "color": "gray" },
|
||||
{ "name": "urgent", "color": "red" }
|
||||
]
|
||||
}
|
||||
}
|
||||
Generated
+442
-39
@@ -19,6 +19,7 @@
|
||||
"tailwindcss": "^3.4.0",
|
||||
"typescript": "~5.5.0",
|
||||
"vite": "^5.4.0",
|
||||
"vitest": "^3.2.7",
|
||||
"vue-tsc": "^2.1.0"
|
||||
}
|
||||
},
|
||||
@@ -640,9 +641,6 @@
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -657,9 +655,6 @@
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -674,9 +669,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -691,9 +683,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -708,9 +697,6 @@
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -725,9 +711,6 @@
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -742,9 +725,6 @@
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -759,9 +739,6 @@
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -776,9 +753,6 @@
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -793,9 +767,6 @@
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -810,9 +781,6 @@
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -827,9 +795,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -844,9 +809,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -937,6 +899,22 @@
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@types/chai": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
|
||||
"integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/deep-eql": "*",
|
||||
"assertion-error": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/deep-eql": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
|
||||
"integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
|
||||
@@ -958,6 +936,123 @@
|
||||
"vue": "^3.2.25"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/expect": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.7.tgz",
|
||||
"integrity": "sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/chai": "^5.2.2",
|
||||
"@vitest/spy": "3.2.7",
|
||||
"@vitest/utils": "3.2.7",
|
||||
"chai": "^5.2.0",
|
||||
"tinyrainbow": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/mocker": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.7.tgz",
|
||||
"integrity": "sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@vitest/spy": "3.2.7",
|
||||
"estree-walker": "^3.0.3",
|
||||
"magic-string": "^0.30.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"msw": "^2.4.9",
|
||||
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"msw": {
|
||||
"optional": true
|
||||
},
|
||||
"vite": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/mocker/node_modules/estree-walker": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
|
||||
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/pretty-format": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.7.tgz",
|
||||
"integrity": "sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"tinyrainbow": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/runner": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.7.tgz",
|
||||
"integrity": "sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@vitest/utils": "3.2.7",
|
||||
"pathe": "^2.0.3",
|
||||
"strip-literal": "^3.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/snapshot": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.7.tgz",
|
||||
"integrity": "sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "3.2.7",
|
||||
"magic-string": "^0.30.17",
|
||||
"pathe": "^2.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/spy": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.7.tgz",
|
||||
"integrity": "sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"tinyspy": "^4.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/utils": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.7.tgz",
|
||||
"integrity": "sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "3.2.7",
|
||||
"loupe": "^3.1.4",
|
||||
"tinyrainbow": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@volar/language-core": {
|
||||
"version": "2.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.15.tgz",
|
||||
@@ -1162,6 +1257,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/assertion-error": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
|
||||
"integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/autoprefixer": {
|
||||
"version": "10.5.4",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.4.tgz",
|
||||
@@ -1289,6 +1393,15 @@
|
||||
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
||||
}
|
||||
},
|
||||
"node_modules/cac": {
|
||||
"version": "6.7.14",
|
||||
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
|
||||
"integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/camelcase-css": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
|
||||
@@ -1320,6 +1433,31 @@
|
||||
],
|
||||
"license": "CC-BY-4.0"
|
||||
},
|
||||
"node_modules/chai": {
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz",
|
||||
"integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"assertion-error": "^2.0.1",
|
||||
"check-error": "^2.1.1",
|
||||
"deep-eql": "^5.0.1",
|
||||
"loupe": "^3.1.0",
|
||||
"pathval": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/check-error": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz",
|
||||
"integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
@@ -1394,6 +1532,32 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/deep-eql": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
|
||||
"integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/didyoumean": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
||||
@@ -1437,6 +1601,12 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-module-lexer": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
|
||||
"integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
|
||||
@@ -1492,6 +1662,15 @@
|
||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/expect-type": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz",
|
||||
"integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-glob": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
|
||||
@@ -1692,6 +1871,12 @@
|
||||
"jiti": "bin/jiti.js"
|
||||
}
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz",
|
||||
"integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lilconfig": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
|
||||
@@ -1712,6 +1897,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/loupe": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz",
|
||||
"integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.21",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||
@@ -1761,6 +1952,12 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/muggle-string": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz",
|
||||
@@ -1852,6 +2049,21 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pathe": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
|
||||
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/pathval": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz",
|
||||
"integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 14.16"
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
@@ -2221,6 +2433,12 @@
|
||||
"queue-microtask": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/siginfo": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
|
||||
"integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
@@ -2230,6 +2448,30 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stackback": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
|
||||
"integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/std-env": {
|
||||
"version": "3.10.0",
|
||||
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
|
||||
"integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/strip-literal": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz",
|
||||
"integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"js-tokens": "^9.0.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/sucrase": {
|
||||
"version": "3.35.1",
|
||||
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz",
|
||||
@@ -2327,6 +2569,18 @@
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/tinybench": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
|
||||
"integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/tinyexec": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
|
||||
"integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.17",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
|
||||
@@ -2375,6 +2629,33 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/tinypool": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz",
|
||||
"integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^18.0.0 || >=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyrainbow": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz",
|
||||
"integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyspy": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.6.tgz",
|
||||
"integrity": "sha512-u8KszXvGfU68hVcZpRHKG28T0krMuv2G5nDhiHaMLen/gIuFEgIJhaJuO69qjnXg5paSrbPMFfx3brNuN8eVSg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
@@ -2507,6 +2788,112 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vite-node": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz",
|
||||
"integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cac": "^6.7.14",
|
||||
"debug": "^4.4.1",
|
||||
"es-module-lexer": "^1.7.0",
|
||||
"pathe": "^2.0.3",
|
||||
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0"
|
||||
},
|
||||
"bin": {
|
||||
"vite-node": "vite-node.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.0.0 || ^20.0.0 || >=22.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/vitest": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.7.tgz",
|
||||
"integrity": "sha512-KrxIJ62Fd89gfysR4WotlgZABiz2dqFPgqGzX7s+CwsqLFomRH7777ZcrOD6+WVAh7khPQP41A+BKbpcJFrdEg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/chai": "^5.2.2",
|
||||
"@vitest/expect": "3.2.7",
|
||||
"@vitest/mocker": "3.2.7",
|
||||
"@vitest/pretty-format": "^3.2.7",
|
||||
"@vitest/runner": "3.2.7",
|
||||
"@vitest/snapshot": "3.2.7",
|
||||
"@vitest/spy": "3.2.7",
|
||||
"@vitest/utils": "3.2.7",
|
||||
"chai": "^5.2.0",
|
||||
"debug": "^4.4.1",
|
||||
"expect-type": "^1.2.1",
|
||||
"magic-string": "^0.30.17",
|
||||
"pathe": "^2.0.3",
|
||||
"picomatch": "^4.0.2",
|
||||
"std-env": "^3.9.0",
|
||||
"tinybench": "^2.9.0",
|
||||
"tinyexec": "^0.3.2",
|
||||
"tinyglobby": "^0.2.14",
|
||||
"tinypool": "^1.1.1",
|
||||
"tinyrainbow": "^2.0.0",
|
||||
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0",
|
||||
"vite-node": "3.2.4",
|
||||
"why-is-node-running": "^2.3.0"
|
||||
},
|
||||
"bin": {
|
||||
"vitest": "vitest.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.0.0 || ^20.0.0 || >=22.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@edge-runtime/vm": "*",
|
||||
"@types/debug": "^4.1.12",
|
||||
"@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
|
||||
"@vitest/browser": "3.2.7",
|
||||
"@vitest/ui": "3.2.7",
|
||||
"happy-dom": "*",
|
||||
"jsdom": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@edge-runtime/vm": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/debug": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
},
|
||||
"@vitest/browser": {
|
||||
"optional": true
|
||||
},
|
||||
"@vitest/ui": {
|
||||
"optional": true
|
||||
},
|
||||
"happy-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"jsdom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vitest/node_modules/picomatch": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
|
||||
"integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/vscode-uri": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz",
|
||||
@@ -2592,6 +2979,22 @@
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/why-is-node-running": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
|
||||
"integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"siginfo": "^2.0.0",
|
||||
"stackback": "0.0.2"
|
||||
},
|
||||
"bin": {
|
||||
"why-is-node-running": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc --noEmit && vite build",
|
||||
"preview": "vite preview"
|
||||
"preview": "vite preview",
|
||||
"test": "vitest run"
|
||||
},
|
||||
"dependencies": {
|
||||
"pinia": "^2.2.0",
|
||||
@@ -20,6 +21,7 @@
|
||||
"tailwindcss": "^3.4.0",
|
||||
"typescript": "~5.5.0",
|
||||
"vite": "^5.4.0",
|
||||
"vitest": "^3.2.7",
|
||||
"vue-tsc": "^2.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,9 +169,9 @@ export const NOTE_COLOR_LABELS: Record<NoteColor, string> = {
|
||||
// three implementations, and the same reason: a value that disagrees across surfaces
|
||||
// is a bug you cannot unsee and cannot explain.
|
||||
//
|
||||
// The Kotlin side has a unit test pinning the fixture below. THIS SIDE HAS NO
|
||||
// MECHANICAL GUARD — the frontend has no test runner, only `vue-tsc --noEmit`.
|
||||
// If you change anything here, check it against the fixture by hand.
|
||||
// Both sides are pinned to the same values: `core/testdata/grammar.json` (`tint`),
|
||||
// which `notes/grammar.test.ts` reads, and DerivedTintTest.kt, which writes the same
|
||||
// numbers out by hand. Change the hash or the key order and both go red.
|
||||
export const DERIVED_TINT_KEYS: readonly NoteColor[] = NOTE_COLOR_KEYS.filter(
|
||||
(key) => key !== "default",
|
||||
);
|
||||
@@ -235,22 +235,8 @@ export function resolveLabelColor(label: { name: string; color?: string | null }
|
||||
return derivedTint(label.name.toLowerCase());
|
||||
}
|
||||
|
||||
// Fixture — the same names and expected keys the Kotlin test asserts. Kept here as
|
||||
// prose because there is nowhere on this side to assert it. If you change the hash or
|
||||
// the key order, these must still hold on BOTH surfaces.
|
||||
//
|
||||
// The raw hash, over four UUIDs — ids no longer pick a colour, but they are what the
|
||||
// hash itself is pinned by and the Kotlin test still asserts them:
|
||||
//
|
||||
// 00000000-0000-0000-0000-000000000000 0xbe478ed1 purple
|
||||
// 11111111-1111-1111-1111-111111111111 0x3d75cc01 blue
|
||||
// 6ba7b810-9dad-11d1-80b4-00c04fd430c8 0xf108e530 orange
|
||||
// f47ac10b-58cc-4372-a567-0e02b2c3d479 0x5b651540 orange
|
||||
//
|
||||
// And the live path — a label, hashing its lowercased NAME:
|
||||
//
|
||||
// todo -> pink grocery -> blue work -> green home -> gray
|
||||
// ideas -> green reading -> gray urgent -> red
|
||||
// The fixture (four UUID hashes and seven tag names) lives in
|
||||
// core/testdata/grammar.json under `tint`.
|
||||
//
|
||||
// Note `work`/`ideas` and `home`/`reading` collide. Nine keys makes that unavoidable
|
||||
// and it is not a bug: colour hints that two tags are distinct, it never claims two
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
// The web's half of the shared grammar fixture (core/testdata/grammar.json).
|
||||
//
|
||||
// The same cases run against the core (derive.rs) and the server (checklist.py,
|
||||
// tags.py). Until this file the web copy had no mechanical guard at all — the
|
||||
// frontend's only CI lane was `vue-tsc --noEmit` — so a drift here showed up as a
|
||||
// checklist or a tag colour that differed in the browser and nowhere else.
|
||||
import { describe, expect, it } from "vitest";
|
||||
import fixture from "../../../core/testdata/grammar.json";
|
||||
import { DERIVED_TINT_KEYS, derivedTint, resolveLabelColor, tintHash } from "./colors";
|
||||
import { parseInline, parseTaskLine, renderTaskLine } from "./markdown";
|
||||
|
||||
describe("checklist lines", () => {
|
||||
it.each(fixture.task_lines)("$line", ({ line, item }) => {
|
||||
expect(parseTaskLine(line)).toEqual(item);
|
||||
});
|
||||
|
||||
it.each(fixture.rendered_items)("renders $line", ({ text, checked, line }) => {
|
||||
expect(renderTaskLine(text, checked)).toBe(line);
|
||||
});
|
||||
});
|
||||
|
||||
describe("tags", () => {
|
||||
// The renderer finds tags; it does not collect them. Collected here line by line
|
||||
// and deduped the way the derivers dedupe — first spelling wins, case-insensitively.
|
||||
function tagsOf(body: string): string[] {
|
||||
const out: string[] = [];
|
||||
for (const line of body.split("\n")) {
|
||||
for (const token of parseInline(line)) {
|
||||
if (token.type !== "tag") continue;
|
||||
if (!out.some((t) => t.toLowerCase() === token.value.toLowerCase())) out.push(token.value);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
it.each(fixture.tags)("$body", ({ body, tags }) => {
|
||||
expect(tagsOf(body)).toEqual(tags);
|
||||
});
|
||||
});
|
||||
|
||||
describe("derived tag colour", () => {
|
||||
it("uses the same key order as Android", () => {
|
||||
expect([...DERIVED_TINT_KEYS]).toEqual(fixture.tint.keys);
|
||||
});
|
||||
|
||||
it.each(fixture.tint.hashes)("hashes $id", ({ id, hash, color }) => {
|
||||
expect(tintHash(id)).toBe(hash);
|
||||
expect(derivedTint(id)).toBe(color);
|
||||
});
|
||||
|
||||
it.each(fixture.tint.names)("colours #$name", ({ name, color }) => {
|
||||
expect(resolveLabelColor({ name })).toBe(color);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createPinia, setActivePinia } from "pinia";
|
||||
|
||||
vi.mock("../adapters", () => ({ repo: { notes: { titles: vi.fn() } } }));
|
||||
|
||||
import { repo } from "../adapters";
|
||||
import { useTitlesStore } from "./titles";
|
||||
|
||||
describe("the palette's note titles", () => {
|
||||
beforeEach(() => setActivePinia(createPinia()));
|
||||
|
||||
// #5165: the list loaded once per session, so a note written after the palette
|
||||
// first opened could not be found by name until the page reloaded.
|
||||
it("sees a note written since the last open", async () => {
|
||||
vi.mocked(repo.notes.titles)
|
||||
.mockResolvedValueOnce([{ id: "a", title: "Groceries" }])
|
||||
.mockResolvedValueOnce([
|
||||
{ id: "a", title: "Groceries" },
|
||||
{ id: "b", title: "Trip" },
|
||||
]);
|
||||
const titles = useTitlesStore();
|
||||
|
||||
await titles.refresh();
|
||||
expect(titles.items.map((t) => t.title)).toEqual(["Groceries"]);
|
||||
|
||||
await titles.refresh();
|
||||
expect(titles.items.map((t) => t.title)).toEqual(["Groceries", "Trip"]);
|
||||
});
|
||||
|
||||
it("keeps the last list when a refresh fails", async () => {
|
||||
vi.mocked(repo.notes.titles)
|
||||
.mockResolvedValueOnce([{ id: "a", title: "Groceries" }])
|
||||
.mockRejectedValueOnce(new Error("offline"));
|
||||
const titles = useTitlesStore();
|
||||
|
||||
await titles.refresh();
|
||||
await expect(titles.refresh()).rejects.toThrow("offline");
|
||||
expect(titles.items.map((t) => t.title)).toEqual(["Groceries"]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
"""The server's half of the shared grammar fixture (core/testdata/grammar.json).
|
||||
|
||||
The same cases run against the core (derive.rs) and the web (markdown.ts). A case
|
||||
that passes in one language and fails in another is a note that changes shape when
|
||||
it syncs — a checklist that stops being one, or a tag that is a label on the phone
|
||||
and plain text on the server.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from inkwell.notes.checklist import parse_items, render_item
|
||||
from inkwell.notes.tags import parse_tags, split_body_tags
|
||||
|
||||
FIXTURE = json.loads(
|
||||
(Path(__file__).resolve().parents[1] / "core" / "testdata" / "grammar.json").read_text(encoding="utf-8")
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("case", FIXTURE["task_lines"], ids=lambda c: repr(c["line"]))
|
||||
def test_task_lines(case):
|
||||
got = [(i.text, i.checked) for i in parse_items(case["line"])]
|
||||
want = [] if case["item"] is None else [(case["item"]["text"], case["item"]["checked"])]
|
||||
assert got == want
|
||||
|
||||
|
||||
@pytest.mark.parametrize("case", FIXTURE["rendered_items"], ids=lambda c: repr(c["line"]))
|
||||
def test_rendered_items(case):
|
||||
assert render_item(case["text"], case["checked"]) == case["line"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("case", FIXTURE["tags"], ids=lambda c: repr(c["body"]))
|
||||
def test_tags(case):
|
||||
assert parse_tags(case["body"]) == case["tags"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("case", FIXTURE["lifts"], ids=lambda c: repr(c["body"]))
|
||||
def test_lifts(case):
|
||||
standalone, inline, lifted = split_body_tags(case["body"])
|
||||
assert (standalone, inline, lifted) == (case["standalone"], case["inline"], case["lifted"])
|
||||
Reference in New Issue
Block a user