feat(ui): establish shared desktop shell and assets

This commit is contained in:
sechmachine
2026-08-14 23:41:51 +07:00
parent 5967f7f70d
commit bac39812a3
15 changed files with 1822 additions and 0 deletions
@@ -0,0 +1,103 @@
package com.lab.labtimesheet.ui;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.nio.charset.StandardCharsets;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest;
import org.springframework.core.io.ClassPathResource;
import org.springframework.context.annotation.Import;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.web.bind.annotation.GetMapping;
@WebMvcTest(UiContractWebTest.ContractController.class)
@Import(UiContractWebTest.ContractController.class)
class UiContractWebTest {
private final MockMvc mvc;
@Autowired
UiContractWebTest(MockMvc mvc) {
this.mvc = mvc;
}
@Test
@WithMockUser(username = "mentor@example.test", roles = "MENTOR")
void sharedShellRendersAuthorizedDesktopNavigationBeforeDomainPagesIntegrate() throws Exception {
MvcResult result = mvc.perform(get("/ui-contract"))
.andExpect(status().isOk())
.andReturn();
String html = result.getResponse().getContentAsString(StandardCharsets.UTF_8);
assertTrue(html.contains("Lab Timesheet"));
assertTrue(html.contains("Owned Projects"));
assertTrue(html.contains("Theme"));
assertTrue(html.contains("Logout"));
assertFalse(html.contains("Accounts"));
assertFalse(html.contains("My attendance"));
assertTrue(html.indexOf("/assets/theme.js") < html.indexOf("/assets/app.css"));
assertTrue(html.contains("href=\"/assets/icons.svg#panel-left\""));
}
@Test
void compiledAssetsAreLocalAndContainOnlyTheSelectedIconSprite() throws Exception {
ClassPathResource css = new ClassPathResource("static/assets/app.css");
ClassPathResource theme = new ClassPathResource("static/assets/theme.js");
ClassPathResource script = new ClassPathResource("static/assets/app.js");
ClassPathResource sprite = new ClassPathResource("static/assets/icons.svg");
assertTrue(css.exists());
assertTrue(theme.exists());
assertTrue(script.exists());
assertTrue(sprite.exists());
String icons = sprite.getContentAsString(StandardCharsets.UTF_8);
assertTrue(icons.contains("id=\"panel-left\""));
assertTrue(icons.contains("id=\"circle-user-round\""));
assertFalse(icons.contains("<script"));
assertFalse(icons.contains("href=\"http"));
assertFalse(icons.contains("<image"));
String themeBootstrap = theme.getContentAsString(StandardCharsets.UTF_8);
assertTrue(themeBootstrap.contains("localStorage.getItem('labtimesheet-theme')"));
assertTrue(themeBootstrap.contains("matchMedia('(prefers-color-scheme: dark)')"));
}
@Test
@WithMockUser(username = "admin@example.test", roles = "ADMIN")
void sharedComponentsExposeAccessibleFormsStatusAndEmptyState() throws Exception {
String html = mvc.perform(get("/ui-components"))
.andExpect(status().isOk())
.andReturn()
.getResponse()
.getContentAsString(StandardCharsets.UTF_8);
assertTrue(html.contains("for=\"displayName\""));
assertTrue(html.contains("id=\"displayName\""));
assertTrue(html.contains("role=\"alert\""));
assertTrue(html.contains("No records yet"));
assertTrue(html.contains("Status: Active"));
assertTrue(html.contains("aria-describedby=\"confirm-dialog-description\""));
}
@Controller
public static class ContractController {
@GetMapping("/ui-contract")
String contract() {
return "test/layout-consumer";
}
@GetMapping("/ui-components")
String components() {
return "test/components-consumer";
}
}
}
@@ -0,0 +1,10 @@
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<body>
<div th:replace="~{fragments/components :: input('displayName', 'Display name', 'Mai', null)}"></div>
<div th:replace="~{fragments/components :: alert('error', 'Please correct the highlighted fields.')} "></div>
<div th:replace="~{fragments/components :: empty('No records yet', 'Create the first record to begin.')} "></div>
<span th:replace="~{fragments/components :: badge('Active', 'success')}"></span>
<dialog th:replace="~{fragments/components :: confirmation('confirm-dialog', 'Confirm change', 'This action changes the current state.')} "></dialog>
</body>
</html>
@@ -0,0 +1,15 @@
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
th:replace="~{fragments/layout :: shell(
pageTitle='Shared shell contract',
section='Mentor',
activeNav='dashboard',
primaryAction=~{::#primary-action},
content=~{::main})}">
<body>
<a id="primary-action" href="/projects/new">Create Project</a>
<main>
<p>Domain-owned content</p>
</main>
</body>
</html>