diff --git a/src/pages-service/tickets/components/ProductDetail.vue b/src/pages-service/tickets/components/ProductDetail.vue index 6a05c3e..09fd3ac 100644 --- a/src/pages-service/tickets/components/ProductDetail.vue +++ b/src/pages-service/tickets/components/ProductDetail.vue @@ -1,6 +1,26 @@ diff --git a/src/pages-service/tickets/view-contract.test.mjs b/src/pages-service/tickets/view-contract.test.mjs index c2d741f..5cb4ae6 100644 --- a/src/pages-service/tickets/view-contract.test.mjs +++ b/src/pages-service/tickets/view-contract.test.mjs @@ -192,6 +192,19 @@ test("TicketHome prefers mediaList and falls back to coverImage", async () => { assert.doesNotMatch(source, / { + const tabs = await readTicketFile("components/TicketDetailTabs.vue"); + + assert.match(tabs, / { const indexPage = await readTicketFile("index.vue"); const detailPage = await readTicketFile("product-detail.vue"); @@ -214,7 +227,27 @@ test("product detail is an independent prototype-aligned page", async () => { assert.match(detailPage, /=\s*scrollHeight - 4/ + ); assert.match(component, /relative h-56 overflow-hidden bg-white/); assert.doesNotMatch(component, /1 \/ \{\{ productImages\.length \}\}/); assert.doesNotMatch(component, /productImages\.slice/); @@ -235,7 +268,7 @@ test("product detail is an independent prototype-aligned page", async () => { ]) { assert.ok(component.includes(field), `${field} is not rendered`); } - assert.match(component, /v-for="section in detailSections"/); + assert.match(component, /v-for="\(section, index\) in detailSections"/); assert.match(component, /section\.sectionCode/); assert.match(component, /section\.title/); assert.match(component, /section\.content/); @@ -268,6 +301,44 @@ test("product detail is an independent prototype-aligned page", async () => { ); }); +test("product detail tab clicks win until the visitor resumes manual scrolling", async () => { + const component = await readTicketFile("components/ProductDetail.vue"); + const factorySource = component.match( + /const createDetailSectionScrollSelection = \(\) => \{[\s\S]*?^\};?\n/m + )?.[0]; + + assert.ok(factorySource, "inline detail section scroll selection factory missing"); + assert.match(component, /const detailSectionScrollSelection =\s*createDetailSectionScrollSelection\(\)/); + assert.match( + component, + /const selectDetailSection = \(sectionCode\) => \{\s*activeSectionCode\.value = detailSectionScrollSelection\.select/ + ); + assert.match( + component, + /const enableDetailScrollSync = \(\) => \{\s*detailSectionScrollSelection\.resumeScrollSync\(\)/ + ); + assert.match( + component, + /activeSectionCode\.value = detailSectionScrollSelection\.resolve\(/ + ); + + const createDetailSectionScrollSelection = Function( + `${factorySource}\nreturn createDetailSectionScrollSelection` + )(); + const selection = createDetailSectionScrollSelection(); + const clickedSectionCode = selection.select("PURCHASE_NOTICE"); + + assert.equal( + selection.resolve(clickedSectionCode, "PRODUCT_DETAIL"), + "PURCHASE_NOTICE" + ); + selection.resumeScrollSync(); + assert.equal( + selection.resolve(clickedSectionCode, "FEE_INCLUDE"), + "FEE_INCLUDE" + ); +}); + test("booking flow matches the visitor prototype while preserving requested controls", async () => { const component = await readTicketFile("components/BookingFlow.vue"); const page = await readTicketFile("booking.vue");