diff --git a/docs/tests/web/dark-icon-sprite-presentation.md b/docs/tests/web/dark-icon-sprite-presentation.md new file mode 100644 index 0000000..7207175 --- /dev/null +++ b/docs/tests/web/dark-icon-sprite-presentation.md @@ -0,0 +1,70 @@ +# Test Evidence: dark icon sprite presentation + +- **Test type:** Web +- **Requirement IDs:** `UI-006`, `UI-009`, `UI-010`, `UI-018` +- **Scenario IDs:** `AC-UI-003`, `AC-UI-005` +- **Test class/method:** `com.lab.labtimesheet.ui.UiContractWebTest#generatedLucideSymbolsRetainCurrentColorStrokePresentation` +- **Implementation commit:** `pending` + +## Protected behavior + +Every local Lucide sprite symbol retains the source presentation attributes so icons referenced with `` inherit `currentColor` rather than rendering with the SVG default black fill on dark surfaces. + +## Test method + +The focused web contract reads the generated classpath sprite, scans every emitted ``, and checks the five presentation attributes on each symbol. It checks the deployable generated artifact rather than generator source text. + +## Hand-derived expected result + +Lucide 1.27.0 line icons use `fill="none"`, `stroke="currentColor"`, `stroke-width="2"`, `stroke-linecap="round"`, and `stroke-linejoin="round"` on their SVG root. Each selected generated symbol must preserve those values. + +## RED + +**Command** + +```text +env JAVA_HOME=/opt/homebrew/opt/openjdk@25 PATH=/opt/homebrew/opt/openjdk@25/bin:$PATH ./mvnw '-Dtest=UiContractWebTest#generatedLucideSymbolsRetainCurrentColorStrokePresentation' test +``` + +**Observed result** + +```text +UiContractWebTest.generatedLucideSymbolsRetainCurrentColorStrokePresentation +Missing fill on id="bell" viewBox="0 0 24 24" ==> expected: but was: +Tests run: 1, Failures: 1, Errors: 0, Skipped: 0 +BUILD FAILURE +``` + +## GREEN + +**Command** + +```text +env PATH=/opt/homebrew/opt/node@24/bin:$PATH npm ci +env PATH=/opt/homebrew/opt/node@24/bin:$PATH npm run build +env JAVA_HOME=/opt/homebrew/opt/openjdk@25 PATH=/opt/homebrew/opt/openjdk@25/bin:$PATH ./mvnw '-Dtest=UiContractWebTest#generatedLucideSymbolsRetainCurrentColorStrokePresentation' test +``` + +**Observed result** + +```text +Node v24.19.0 and npm 11.17.0 installed the locked dependencies. +Tailwind CSS v4.3.3 rebuilt app.css and build-icons regenerated icons.svg. +Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 +BUILD SUCCESS +``` + +## Affected suite + +**Command and result** + +```text +env JAVA_HOME=/opt/homebrew/opt/openjdk@25 PATH=/opt/homebrew/opt/openjdk@25/bin:$PATH ./mvnw '-Dtest=UiContractWebTest' test + +Tests run: 7, Failures: 0, Errors: 0, Skipped: 0 +BUILD SUCCESS +``` + +## External-test boundaries + +The deterministic asset contract proves the generated sprite carries theme-aware Lucide presentation attributes. It does not replace the taskmaster-owned integrated browser/detector pass for rendered layout and interactive states. diff --git a/src/main/frontend/build-icons.mjs b/src/main/frontend/build-icons.mjs index 086f575..6f584e8 100644 --- a/src/main/frontend/build-icons.mjs +++ b/src/main/frontend/build-icons.mjs @@ -10,10 +10,14 @@ const names = [ const output = resolve('src/main/resources/static/assets/icons.svg'); const symbols = await Promise.all(names.map(async (name) => { const svg = await readFile(resolve(`node_modules/lucide-static/icons/${name}.svg`), 'utf8'); - const viewBox = svg.match(/viewBox="([^"]+)"/)?.[1] ?? '0 0 24 24'; + const root = svg.match(/]*)>/)?.[1]; + const viewBox = root?.match(/viewBox="([^"]+)"/)?.[1] ?? '0 0 24 24'; + const presentation = ['fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin'] + .map((attribute) => root?.match(new RegExp(`${attribute}="[^"]+"`))?.[0]) + .join(' '); const body = svg.match(/([\s\S]*?)<\/svg>/)?.[1]; if (!body) throw new Error(`Invalid Lucide SVG: ${name}`); - return `${body.trim()}`; + return `${body.trim()}`; })); await mkdir(dirname(output), { recursive: true }); diff --git a/src/main/resources/static/assets/icons.svg b/src/main/resources/static/assets/icons.svg index 8a608d2..3f6b6f2 100644 --- a/src/main/resources/static/assets/icons.svg +++ b/src/main/resources/static/assets/icons.svg @@ -1,5 +1,5 @@ - - + + @@ -8,27 +8,27 @@ - - + + - - + + - - + + - + - + - + - - - + + + @@ -36,10 +36,10 @@ - + - + - + diff --git a/src/test/java/com/lab/labtimesheet/ui/UiContractWebTest.java b/src/test/java/com/lab/labtimesheet/ui/UiContractWebTest.java index 9e2c61e..81409bc 100644 --- a/src/test/java/com/lab/labtimesheet/ui/UiContractWebTest.java +++ b/src/test/java/com/lab/labtimesheet/ui/UiContractWebTest.java @@ -97,6 +97,26 @@ class UiContractWebTest { assertTrue(themeBootstrap.contains("matchMedia('(prefers-color-scheme: dark)')")); } + @Test + void generatedLucideSymbolsRetainCurrentColorStrokePresentation() throws Exception { + String icons = new ClassPathResource("static/assets/icons.svg") + .getContentAsString(StandardCharsets.UTF_8); + Matcher symbols = Pattern.compile("]*)>").matcher(icons); + int symbolCount = 0; + + while (symbols.find()) { + String attributes = symbols.group(1); + assertTrue(attributes.contains("fill=\"none\""), () -> "Missing fill on " + attributes); + assertTrue(attributes.contains("stroke=\"currentColor\""), () -> "Missing stroke on " + attributes); + assertTrue(attributes.contains("stroke-width=\"2\""), () -> "Missing stroke width on " + attributes); + assertTrue(attributes.contains("stroke-linecap=\"round\""), () -> "Missing stroke linecap on " + attributes); + assertTrue(attributes.contains("stroke-linejoin=\"round\""), () -> "Missing stroke linejoin on " + attributes); + symbolCount++; + } + + assertTrue(symbolCount > 0, "The generated sprite must contain symbols"); + } + @Test @WithMockUser(username = "admin@example.test", roles = "ADMIN") void collapsedSidebarExposesStateAndKeyboardVisibleControlNames() throws Exception {