srcdev-nuxt-components 9.3.10 → 9.3.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/.claude/skills/component-aria-landmark.md +3 -3
  2. package/.claude/skills/components/auto-grid.md +1 -1
  3. package/.claude/skills/components/page-hero-highlights.md +1 -1
  4. package/.claude/skills/components/service-detail.md +13 -14
  5. package/.claude/skills/components/service-summary-grid.md +141 -0
  6. package/.claude/skills/components/service-summary.md +165 -0
  7. package/.claude/skills/icon-sets.md +1 -1
  8. package/.claude/skills/index.md +2 -2
  9. package/.vscode/srcdev-component-service-summary.code-snippets +67 -0
  10. package/app/components/03.organisms/services/service-detail/CONSUMER-STYLING.md +8 -9
  11. package/app/components/03.organisms/services/service-detail/ServiceDetail.vue +3 -2
  12. package/app/components/03.organisms/services/service-detail/stories/ServiceDetail.stories.ts +2 -2
  13. package/app/components/03.organisms/services/service-summary/CONSUMER-STYLING.md +116 -0
  14. package/app/components/03.organisms/services/service-summary/ServiceSummary.vue +183 -0
  15. package/app/components/03.organisms/services/{services-section/stories/ServicesSection.stories.ts → service-summary/stories/ServiceSummary.stories.ts} +78 -148
  16. package/app/components/03.organisms/services/service-summary/tests/ServiceSummary.spec.ts +210 -0
  17. package/app/components/03.organisms/services/service-summary/tests/__snapshots__/ServiceSummary.spec.ts.snap +17 -0
  18. package/app/components/03.organisms/services/services-grids/{ServicesSectionGrid.vue → ServiceSummaryGrid.vue} +8 -10
  19. package/app/components/03.organisms/services/services-grids/stories/{ServicesSectionGrid.stories.ts → ServiceSummaryGrid.stories.ts} +21 -21
  20. package/app/components/03.organisms/services/services-grids/tests/{ServicesSectionGrid.spec.ts → ServiceSummaryGrid.spec.ts} +28 -28
  21. package/app/components/03.organisms/services/services-grids/tests/__snapshots__/ServiceSummaryGrid.spec.ts.snap +45 -0
  22. package/package.json +1 -1
  23. package/.claude/skills/components/services-section-grid.md +0 -130
  24. package/.claude/skills/components/services-section.md +0 -211
  25. package/.vscode/srcdev-component-services-section.code-snippets +0 -84
  26. package/app/components/03.organisms/services/services-grids/tests/__snapshots__/ServicesSectionGrid.spec.ts.snap +0 -87
  27. package/app/components/03.organisms/services/services-section/CONSUMER-STYLING.md +0 -149
  28. package/app/components/03.organisms/services/services-section/ServicesSection.vue +0 -345
  29. package/app/components/03.organisms/services/services-section/tests/ServicesSection.spec.ts +0 -297
  30. package/app/components/03.organisms/services/services-section/tests/__snapshots__/ServicesSection.spec.ts.snap +0 -87
@@ -0,0 +1,210 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { mountSuspended } from "@nuxt/test-utils/runtime";
3
+ import ServiceSummary from "../ServiceSummary.vue";
4
+ import type { Service } from "~/types/types.services";
5
+
6
+ // ─── Fixtures ─────────────────────────────────────────────────────────────────
7
+
8
+ const mockService: Service = {
9
+ slug: "test-service",
10
+ category: "hair",
11
+ title: "Test Service",
12
+ subtitle: "A subtitle",
13
+ price: "£50",
14
+ duration: "60 mins",
15
+ image: "/images/test.jpg",
16
+ shortDescription: "Short description",
17
+ longDescription: "Long description text",
18
+ heroHeading: [{ text: "A great heading", styleClass: "normal" }],
19
+ whatIsIt: "What this service is",
20
+ process: ["Step one", "Step two"],
21
+ idealFor: ["Person A", "Person B"],
22
+ maintenance: "Maintenance advice",
23
+ faqs: [{ question: "FAQ question?", answer: "FAQ answer." }],
24
+ seoTitle: "SEO Title",
25
+ seoDescription: "SEO description",
26
+ };
27
+
28
+ // ─── Tests ────────────────────────────────────────────────────────────────────
29
+
30
+ describe("ServiceSummary", () => {
31
+ // ─── Mount ─────────────────────────────────────────────────────────────
32
+
33
+ it("mounts without error", async () => {
34
+ const wrapper = await mountSuspended(ServiceSummary, {
35
+ props: { serviceData: mockService },
36
+ });
37
+ expect(wrapper.vm).toBeTruthy();
38
+ });
39
+
40
+ it("renders correct HTML structure", async () => {
41
+ const wrapper = await mountSuspended(ServiceSummary, {
42
+ props: { serviceData: mockService },
43
+ });
44
+ expect(wrapper.html()).toMatchSnapshot();
45
+ });
46
+
47
+ // ─── Root element ───────────────────────────────────────────────────────
48
+
49
+ it("has service-summary class on root", async () => {
50
+ const wrapper = await mountSuspended(ServiceSummary, {
51
+ props: { serviceData: mockService },
52
+ });
53
+ expect(wrapper.classes()).toContain("service-summary");
54
+ });
55
+
56
+ it("renders as a div by default", async () => {
57
+ const wrapper = await mountSuspended(ServiceSummary, {
58
+ props: { serviceData: mockService },
59
+ });
60
+ expect(wrapper.element.tagName).toBe("DIV");
61
+ });
62
+
63
+ it("renders the tag prop as the root element", async () => {
64
+ const wrapper = await mountSuspended(ServiceSummary, {
65
+ props: { serviceData: mockService, tag: "section" },
66
+ });
67
+ expect(wrapper.element.tagName).toBe("SECTION");
68
+ });
69
+
70
+ // ─── Aria ───────────────────────────────────────────────────────────────
71
+
72
+ it("binds aria-labelledby to the title HeroText's own id when tag is section", async () => {
73
+ const wrapper = await mountSuspended(ServiceSummary, {
74
+ props: { serviceData: mockService, tag: "section" },
75
+ attachTo: document.body,
76
+ });
77
+ const ariaLabelledby = wrapper.attributes("aria-labelledby");
78
+ expect(ariaLabelledby).toBeTruthy();
79
+ expect(document.getElementById(ariaLabelledby!)?.textContent).toContain(mockService.title);
80
+ });
81
+
82
+ // ─── Pills ──────────────────────────────────────────────────────────────
83
+
84
+ it("renders duration and price as DisplayPill instances", async () => {
85
+ const wrapper = await mountSuspended(ServiceSummary, {
86
+ props: { serviceData: mockService },
87
+ });
88
+ const pills = wrapper.findAll(".display-pill");
89
+ expect(pills).toHaveLength(2);
90
+ expect(pills[0]?.text()).toContain(mockService.duration);
91
+ expect(pills[1]?.text()).toContain(mockService.price);
92
+ });
93
+
94
+ // ─── Service data ───────────────────────────────────────────────────────
95
+
96
+ it("renders service duration text", async () => {
97
+ const wrapper = await mountSuspended(ServiceSummary, {
98
+ props: { serviceData: mockService },
99
+ });
100
+ expect(wrapper.text()).toContain(mockService.duration);
101
+ });
102
+
103
+ it("renders service price text", async () => {
104
+ const wrapper = await mountSuspended(ServiceSummary, {
105
+ props: { serviceData: mockService },
106
+ });
107
+ expect(wrapper.text()).toContain(mockService.price);
108
+ });
109
+
110
+ it("renders the whatIsIt text", async () => {
111
+ const wrapper = await mountSuspended(ServiceSummary, {
112
+ props: { serviceData: mockService },
113
+ });
114
+ expect(wrapper.text()).toContain(mockService.whatIsIt);
115
+ });
116
+
117
+ it("does not render process, idealFor, maintenance, or FAQ content", async () => {
118
+ const wrapper = await mountSuspended(ServiceSummary, {
119
+ props: { serviceData: mockService },
120
+ });
121
+ expect(wrapper.text()).not.toContain(mockService.longDescription);
122
+ expect(wrapper.text()).not.toContain(mockService.maintenance);
123
+ expect(wrapper.text()).not.toContain(mockService.faqs[0]?.question);
124
+ });
125
+
126
+ // ─── summary-link slot ────────────────────────────────────────────────
127
+
128
+ it("renders summary-link slot content", async () => {
129
+ const wrapper = await mountSuspended(ServiceSummary, {
130
+ props: { serviceData: mockService },
131
+ slots: {
132
+ "summary-link": '<a class="test-link" href="/services/test">View service</a>',
133
+ },
134
+ });
135
+ expect(wrapper.find(".test-link").exists()).toBe(true);
136
+ });
137
+
138
+ it("passes serviceData to the summary-link slot", async () => {
139
+ const wrapper = await mountSuspended(ServiceSummary, {
140
+ props: { serviceData: mockService },
141
+ slots: {
142
+ "summary-link": `<template #summary-link="{ serviceData }"><a class="test-link">{{ serviceData.title }}</a></template>`,
143
+ },
144
+ });
145
+ expect(wrapper.find(".test-link").text()).toBe(mockService.title);
146
+ });
147
+
148
+ // ─── Reverse ────────────────────────────────────────────────────────────
149
+
150
+ it("applies reverse class when reverse prop is true", async () => {
151
+ const wrapper = await mountSuspended(ServiceSummary, {
152
+ props: { serviceData: mockService, reverse: true },
153
+ });
154
+ expect(wrapper.find(".service-summary__grid").classes()).toContain("service-summary__grid--reverse");
155
+ });
156
+
157
+ it("does not apply reverse class by default", async () => {
158
+ const wrapper = await mountSuspended(ServiceSummary, {
159
+ props: { serviceData: mockService },
160
+ });
161
+ expect(wrapper.find(".service-summary__grid").classes()).not.toContain("service-summary__grid--reverse");
162
+ });
163
+
164
+ // ─── Alignment ──────────────────────────────────────────────────────────
165
+
166
+ it("defaults to center alignment", async () => {
167
+ const wrapper = await mountSuspended(ServiceSummary, {
168
+ props: { serviceData: mockService },
169
+ });
170
+ expect(wrapper.find(".service-summary__info-wrapper").classes()).toContain(
171
+ "service-summary__info-wrapper--align-center"
172
+ );
173
+ });
174
+
175
+ it("applies the alignment prop", async () => {
176
+ const wrapper = await mountSuspended(ServiceSummary, {
177
+ props: { serviceData: mockService, alignment: "start" },
178
+ });
179
+ expect(wrapper.find(".service-summary__info-wrapper").classes()).toContain(
180
+ "service-summary__info-wrapper--align-start"
181
+ );
182
+ });
183
+
184
+ // ─── styleClassPassthrough ──────────────────────────────────────────────
185
+
186
+ it("applies a single styleClassPassthrough string", async () => {
187
+ const wrapper = await mountSuspended(ServiceSummary, {
188
+ props: { serviceData: mockService, styleClassPassthrough: "custom-class" },
189
+ });
190
+ expect(wrapper.classes()).toContain("custom-class");
191
+ });
192
+
193
+ it("applies multiple styleClassPassthrough classes from an array", async () => {
194
+ const wrapper = await mountSuspended(ServiceSummary, {
195
+ props: { serviceData: mockService, styleClassPassthrough: ["class-a", "class-b"] },
196
+ });
197
+ expect(wrapper.classes()).toContain("class-a");
198
+ expect(wrapper.classes()).toContain("class-b");
199
+ });
200
+
201
+ it("updates classes when styleClassPassthrough prop changes", async () => {
202
+ const wrapper = await mountSuspended(ServiceSummary, {
203
+ props: { serviceData: mockService, styleClassPassthrough: ["original"] },
204
+ });
205
+ expect(wrapper.classes()).toContain("original");
206
+ await wrapper.setProps({ styleClassPassthrough: ["updated"] });
207
+ expect(wrapper.classes()).not.toContain("original");
208
+ expect(wrapper.classes()).toContain("updated");
209
+ });
210
+ });
@@ -0,0 +1,17 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`ServiceSummary > renders correct HTML structure 1`] = `
4
+ "<div class="service-summary">
5
+ <div class="service-summary__grid">
6
+ <div class="service-summary__image-wrapper"><img data-nuxt-img="" srcset="/_ipx/_/images/test.jpg 1x, /_ipx/_/images/test.jpg 2x" alt="Test Service" loading="eager" fetchpriority="high" class="service-summary__image" src="/_ipx/_/images/test.jpg"></div>
7
+ <div class="service-summary__info-wrapper service-summary__info-wrapper--align-center">
8
+ <div class="eyebrow-text large">A subtitle</div>
9
+ <h2 id="v-0-0" class="hero-text mb-20 title axis-horizontal">
10
+ <!--v-if--><span class="text-block-0 normal">Test Service</span>
11
+ </h2>
12
+ <div class="service-summary__pills"><span class="display-pill md neutral"><span class="pill-label">60 mins</span></span><span class="display-pill md neutral"><span class="pill-label">From £50</span></span></div>
13
+ <p class="page-body-normal">What this service is</p>
14
+ </div>
15
+ </div>
16
+ </div>"
17
+ `;
@@ -1,18 +1,17 @@
1
1
  <template>
2
- <component :is="tag" class="services-grid" :class="[elementClasses]">
3
- <ServicesSection
2
+ <component :is="tag" class="service-summary-grid" :class="[elementClasses]">
3
+ <ServiceSummary
4
4
  v-for="(item, index) in servicesData"
5
5
  :key="index"
6
6
  :index="index"
7
7
  :service-data="item"
8
- :is-summary="true"
9
8
  :reverse="props.useAlternateReverse ? index % 2 !== 0 : false"
10
- :summary-alignment="summaryAlignment"
9
+ :alignment="alignment"
11
10
  >
12
11
  <template #summary-link="{ serviceData }">
13
12
  <slot name="summary-link" :service-data="serviceData"></slot>
14
13
  </template>
15
- </ServicesSection>
14
+ </ServiceSummary>
16
15
  </component>
17
16
  </template>
18
17
 
@@ -23,14 +22,14 @@ interface Props {
23
22
  tag?: "div" | "section" | "main";
24
23
  servicesData: Service[];
25
24
  useAlternateReverse?: boolean;
26
- summaryAlignment?: "start" | "center" | "end";
25
+ alignment?: "start" | "center" | "end";
27
26
  styleClassPassthrough?: string | string[];
28
27
  }
29
28
 
30
29
  const props = withDefaults(defineProps<Props>(), {
31
30
  tag: "div",
32
31
  useAlternateReverse: false,
33
- summaryAlignment: "center",
32
+ alignment: "center",
34
33
  styleClassPassthrough: () => [],
35
34
  });
36
35
 
@@ -46,11 +45,10 @@ watch(
46
45
 
47
46
  <style lang="css">
48
47
  @layer components {
49
- .services-grid {
50
- /* Component styles */
48
+ .service-summary-grid {
51
49
  display: grid;
52
50
  grid-template-columns: 1fr;
53
- gap: 4rem;
51
+ gap: var(--service-summary-grid-row-gap, 4rem);
54
52
  }
55
53
  }
56
54
  </style>
@@ -1,10 +1,10 @@
1
- import ServicesSectionGrid from "../ServicesSectionGrid.vue";
1
+ import ServiceSummaryGrid from "../ServiceSummaryGrid.vue";
2
2
  import type { Meta, StoryObj } from "@nuxtjs/storybook";
3
3
  import type { Service } from "~/types/types.services";
4
4
 
5
- const meta: Meta<typeof ServicesSectionGrid> = {
6
- title: "Organisms/Services/Services Section Grid",
7
- component: ServicesSectionGrid,
5
+ const meta: Meta<typeof ServiceSummaryGrid> = {
6
+ title: "Organisms/Services/Service Summary Grid",
7
+ component: ServiceSummaryGrid,
8
8
  argTypes: {
9
9
  tag: {
10
10
  control: { type: "select" },
@@ -15,7 +15,7 @@ const meta: Meta<typeof ServicesSectionGrid> = {
15
15
  control: { type: "boolean" },
16
16
  description: "Alternate image/content column order on every other section",
17
17
  },
18
- summaryAlignment: {
18
+ alignment: {
19
19
  control: { type: "select" },
20
20
  options: ["start", "center", "end"],
21
21
  description: "Vertical alignment of the info column in each section",
@@ -28,7 +28,7 @@ const meta: Meta<typeof ServicesSectionGrid> = {
28
28
  args: {
29
29
  tag: "div",
30
30
  useAlternateReverse: false,
31
- summaryAlignment: "center",
31
+ alignment: "center",
32
32
  styleClassPassthrough: [],
33
33
  },
34
34
  parameters: {
@@ -41,14 +41,14 @@ const meta: Meta<typeof ServicesSectionGrid> = {
41
41
  docs: {
42
42
  description: {
43
43
  component:
44
- "Renders a vertical stack of `ServicesSection` components (summary mode) from a `Service[]` array. Each section includes a summary-link and a CTA button. Alternate image/content column order is controlled via `useAlternateReverse`.",
44
+ "Renders a vertical stack of `ServiceSummary` components from a `Service[]` array. Each summary includes a summary-link slot. Alternate image/content column order is controlled via `useAlternateReverse`.",
45
45
  },
46
46
  },
47
47
  },
48
48
  };
49
49
 
50
50
  export default meta;
51
- type Story = StoryObj<typeof ServicesSectionGrid>;
51
+ type Story = StoryObj<typeof ServiceSummaryGrid>;
52
52
 
53
53
  // ─── Fixtures ─────────────────────────────────────────────────────────────────
54
54
 
@@ -82,12 +82,12 @@ const sampleServices: Service[] = [
82
82
 
83
83
  export const Default: Story = {
84
84
  render: (args) => ({
85
- components: { ServicesSectionGrid },
85
+ components: { ServiceSummaryGrid },
86
86
  setup() {
87
87
  return { args, sampleServices };
88
88
  },
89
89
  template: `
90
- <ServicesSectionGrid v-bind="args" :services-data="sampleServices">
90
+ <ServiceSummaryGrid v-bind="args" :services-data="sampleServices">
91
91
  <template #summary-link="{ serviceData }">
92
92
  <a
93
93
  :href="'/services/' + serviceData.slug"
@@ -96,7 +96,7 @@ export const Default: Story = {
96
96
  More about {{ serviceData.title }} →
97
97
  </a>
98
98
  </template>
99
- </ServicesSectionGrid>
99
+ </ServiceSummaryGrid>
100
100
  `,
101
101
  }),
102
102
  parameters: {
@@ -115,18 +115,18 @@ export const AlternateReverse: Story = {
115
115
  useAlternateReverse: true,
116
116
  },
117
117
  render: (args) => ({
118
- components: { ServicesSectionGrid },
118
+ components: { ServiceSummaryGrid },
119
119
  setup() {
120
120
  return { args, sampleServices };
121
121
  },
122
122
  template: `
123
- <ServicesSectionGrid v-bind="args" :services-data="sampleServices">
123
+ <ServiceSummaryGrid v-bind="args" :services-data="sampleServices">
124
124
  <template #summary-link="{ serviceData }">
125
125
  <a :href="'/services/' + serviceData.slug" style="display:inline-flex;margin-top:1.6rem;color:inherit;">
126
126
  More about {{ serviceData.title }} →
127
127
  </a>
128
128
  </template>
129
- </ServicesSectionGrid>
129
+ </ServiceSummaryGrid>
130
130
  `,
131
131
  }),
132
132
  parameters: {
@@ -142,11 +142,11 @@ export const AlternateReverse: Story = {
142
142
  export const NoSlots: Story = {
143
143
  name: "No Slots (empty sections)",
144
144
  render: (args) => ({
145
- components: { ServicesSectionGrid },
145
+ components: { ServiceSummaryGrid },
146
146
  setup() {
147
147
  return { args, sampleServices };
148
148
  },
149
- template: `<ServicesSectionGrid v-bind="args" :services-data="sampleServices" />`,
149
+ template: `<ServiceSummaryGrid v-bind="args" :services-data="sampleServices" />`,
150
150
  }),
151
151
  parameters: {
152
152
  docs: {
@@ -161,18 +161,18 @@ export const NoSlots: Story = {
161
161
  export const SingleService: Story = {
162
162
  name: "Single Service",
163
163
  render: (args) => ({
164
- components: { ServicesSectionGrid },
164
+ components: { ServiceSummaryGrid },
165
165
  setup() {
166
166
  return { args, sampleServices };
167
167
  },
168
168
  template: `
169
- <ServicesSectionGrid v-bind="args" :services-data="[sampleServices[0]]">
169
+ <ServiceSummaryGrid v-bind="args" :services-data="[sampleServices[0]]">
170
170
  <template #summary-link="{ serviceData }">
171
171
  <a :href="'/services/' + serviceData.slug" style="display:inline-flex;margin-top:1.6rem;color:inherit;">
172
172
  More about {{ serviceData.title }} →
173
173
  </a>
174
174
  </template>
175
- </ServicesSectionGrid>
175
+ </ServiceSummaryGrid>
176
176
  `,
177
177
  }),
178
178
  parameters: {
@@ -187,11 +187,11 @@ export const SingleService: Story = {
187
187
  export const EmptyData: Story = {
188
188
  name: "Empty Data",
189
189
  render: (args) => ({
190
- components: { ServicesSectionGrid },
190
+ components: { ServiceSummaryGrid },
191
191
  setup() {
192
192
  return { args };
193
193
  },
194
- template: `<ServicesSectionGrid v-bind="args" :services-data="[]" />`,
194
+ template: `<ServiceSummaryGrid v-bind="args" :services-data="[]" />`,
195
195
  }),
196
196
  parameters: {
197
197
  docs: {
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect } from "vitest";
2
2
  import { mountSuspended } from "@nuxt/test-utils/runtime";
3
- import ServicesSectionGrid from "../ServicesSectionGrid.vue";
3
+ import ServiceSummaryGrid from "../ServiceSummaryGrid.vue";
4
4
  import type { Service } from "~/types/types.services";
5
5
 
6
6
  // ─── Fixtures ─────────────────────────────────────────────────────────────────
@@ -33,18 +33,18 @@ const mockServices: Service[] = [
33
33
 
34
34
  // ─── Tests ────────────────────────────────────────────────────────────────────
35
35
 
36
- describe("ServicesSectionGrid", () => {
36
+ describe("ServiceSummaryGrid", () => {
37
37
  // ─── Mount ─────────────────────────────────────────────────────────────
38
38
 
39
39
  it("mounts without error", async () => {
40
- const wrapper = await mountSuspended(ServicesSectionGrid, {
40
+ const wrapper = await mountSuspended(ServiceSummaryGrid, {
41
41
  props: { servicesData: mockServices },
42
42
  });
43
43
  expect(wrapper.vm).toBeTruthy();
44
44
  });
45
45
 
46
46
  it("renders correct HTML structure", async () => {
47
- const wrapper = await mountSuspended(ServicesSectionGrid, {
47
+ const wrapper = await mountSuspended(ServiceSummaryGrid, {
48
48
  props: { servicesData: mockServices },
49
49
  });
50
50
  expect(wrapper.html()).toMatchSnapshot();
@@ -52,15 +52,15 @@ describe("ServicesSectionGrid", () => {
52
52
 
53
53
  // ─── Root element ───────────────────────────────────────────────────────
54
54
 
55
- it("has services-grid class on root", async () => {
56
- const wrapper = await mountSuspended(ServicesSectionGrid, {
55
+ it("has service-summary-grid class on root", async () => {
56
+ const wrapper = await mountSuspended(ServiceSummaryGrid, {
57
57
  props: { servicesData: mockServices },
58
58
  });
59
- expect(wrapper.classes()).toContain("services-grid");
59
+ expect(wrapper.classes()).toContain("service-summary-grid");
60
60
  });
61
61
 
62
62
  it("renders as a div by default", async () => {
63
- const wrapper = await mountSuspended(ServicesSectionGrid, {
63
+ const wrapper = await mountSuspended(ServiceSummaryGrid, {
64
64
  props: { servicesData: mockServices },
65
65
  });
66
66
  expect(wrapper.element.tagName).toBe("DIV");
@@ -68,7 +68,7 @@ describe("ServicesSectionGrid", () => {
68
68
 
69
69
  it("renders the tag prop as the root element", async () => {
70
70
  for (const tag of ["section", "main"] as const) {
71
- const wrapper = await mountSuspended(ServicesSectionGrid, {
71
+ const wrapper = await mountSuspended(ServiceSummaryGrid, {
72
72
  props: { servicesData: mockServices, tag },
73
73
  });
74
74
  expect(wrapper.element.tagName).toBe(tag.toUpperCase());
@@ -78,51 +78,51 @@ describe("ServicesSectionGrid", () => {
78
78
  // ─── Children ───────────────────────────────────────────────────────────
79
79
 
80
80
  it("renders the correct number of service sections", async () => {
81
- const wrapper = await mountSuspended(ServicesSectionGrid, {
81
+ const wrapper = await mountSuspended(ServiceSummaryGrid, {
82
82
  props: { servicesData: mockServices },
83
83
  });
84
- expect(wrapper.findAll(".services-section")).toHaveLength(mockServices.length);
84
+ expect(wrapper.findAll(".service-summary")).toHaveLength(mockServices.length);
85
85
  });
86
86
 
87
87
  it("renders no sections when servicesData is empty", async () => {
88
- const wrapper = await mountSuspended(ServicesSectionGrid, {
88
+ const wrapper = await mountSuspended(ServiceSummaryGrid, {
89
89
  props: { servicesData: [] },
90
90
  });
91
- expect(wrapper.findAll(".services-section")).toHaveLength(0);
91
+ expect(wrapper.findAll(".service-summary")).toHaveLength(0);
92
92
  });
93
93
 
94
94
  it("renders a single section correctly", async () => {
95
- const wrapper = await mountSuspended(ServicesSectionGrid, {
95
+ const wrapper = await mountSuspended(ServiceSummaryGrid, {
96
96
  props: { servicesData: [mockServices[0]!] },
97
97
  });
98
- expect(wrapper.findAll(".services-section")).toHaveLength(1);
98
+ expect(wrapper.findAll(".service-summary")).toHaveLength(1);
99
99
  });
100
100
 
101
101
  // ─── useAlternateReverse ────────────────────────────────────────────────
102
102
 
103
103
  it("does not apply reverse to any section by default", async () => {
104
- const wrapper = await mountSuspended(ServicesSectionGrid, {
104
+ const wrapper = await mountSuspended(ServiceSummaryGrid, {
105
105
  props: { servicesData: mockServices },
106
106
  });
107
- wrapper.findAll(".services-section__grid").forEach((grid) => {
108
- expect(grid.classes()).not.toContain("services-section__grid--reverse");
107
+ wrapper.findAll(".service-summary__grid").forEach((grid) => {
108
+ expect(grid.classes()).not.toContain("service-summary__grid--reverse");
109
109
  });
110
110
  });
111
111
 
112
112
  it("applies reverse to odd-indexed sections when useAlternateReverse is true", async () => {
113
- const wrapper = await mountSuspended(ServicesSectionGrid, {
113
+ const wrapper = await mountSuspended(ServiceSummaryGrid, {
114
114
  props: { servicesData: mockServices, useAlternateReverse: true },
115
115
  });
116
- const grids = wrapper.findAll(".services-section__grid");
117
- expect(grids[0]?.classes()).not.toContain("services-section__grid--reverse"); // index 0 — even
118
- expect(grids[1]?.classes()).toContain("services-section__grid--reverse"); // index 1 — odd
119
- expect(grids[2]?.classes()).not.toContain("services-section__grid--reverse"); // index 2 — even
116
+ const grids = wrapper.findAll(".service-summary__grid");
117
+ expect(grids[0]?.classes()).not.toContain("service-summary__grid--reverse"); // index 0 — even
118
+ expect(grids[1]?.classes()).toContain("service-summary__grid--reverse"); // index 1 — odd
119
+ expect(grids[2]?.classes()).not.toContain("service-summary__grid--reverse"); // index 2 — even
120
120
  });
121
121
 
122
122
  // ─── Slots ─────────────────────────────────────────────────────────────
123
123
 
124
124
  it("renders summary-link slot content in each section", async () => {
125
- const wrapper = await mountSuspended(ServicesSectionGrid, {
125
+ const wrapper = await mountSuspended(ServiceSummaryGrid, {
126
126
  props: { servicesData: mockServices },
127
127
  slots: {
128
128
  "summary-link": '<a class="test-link" href="/services/test">View service</a>',
@@ -132,7 +132,7 @@ describe("ServicesSectionGrid", () => {
132
132
  });
133
133
 
134
134
  it("renders nothing in summary-link when slot is not provided", async () => {
135
- const wrapper = await mountSuspended(ServicesSectionGrid, {
135
+ const wrapper = await mountSuspended(ServiceSummaryGrid, {
136
136
  props: { servicesData: [mockServices[0]!] },
137
137
  });
138
138
  // isSummary=true, so summary-link slot is rendered — but empty since no content was provided
@@ -142,14 +142,14 @@ describe("ServicesSectionGrid", () => {
142
142
  // ─── styleClassPassthrough ──────────────────────────────────────────────
143
143
 
144
144
  it("applies a single styleClassPassthrough string", async () => {
145
- const wrapper = await mountSuspended(ServicesSectionGrid, {
145
+ const wrapper = await mountSuspended(ServiceSummaryGrid, {
146
146
  props: { servicesData: mockServices, styleClassPassthrough: "custom-grid" },
147
147
  });
148
148
  expect(wrapper.classes()).toContain("custom-grid");
149
149
  });
150
150
 
151
151
  it("applies multiple styleClassPassthrough classes from an array", async () => {
152
- const wrapper = await mountSuspended(ServicesSectionGrid, {
152
+ const wrapper = await mountSuspended(ServiceSummaryGrid, {
153
153
  props: { servicesData: mockServices, styleClassPassthrough: ["class-a", "class-b"] },
154
154
  });
155
155
  expect(wrapper.classes()).toContain("class-a");
@@ -157,7 +157,7 @@ describe("ServicesSectionGrid", () => {
157
157
  });
158
158
 
159
159
  it("updates classes when styleClassPassthrough prop changes", async () => {
160
- const wrapper = await mountSuspended(ServicesSectionGrid, {
160
+ const wrapper = await mountSuspended(ServiceSummaryGrid, {
161
161
  props: { servicesData: mockServices, styleClassPassthrough: ["original"] },
162
162
  });
163
163
  expect(wrapper.classes()).toContain("original");
@@ -0,0 +1,45 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`ServiceSummaryGrid > renders correct HTML structure 1`] = `
4
+ "<div class="service-summary-grid">
5
+ <div class="service-summary">
6
+ <div class="service-summary__grid">
7
+ <div class="service-summary__image-wrapper"><img data-nuxt-img="" srcset="/_ipx/_/images/locs-installation.jpg 1x, /_ipx/_/images/locs-installation.jpg 2x" alt="Locs Installation" loading="eager" fetchpriority="high" class="service-summary__image" src="/_ipx/_/images/locs-installation.jpg"></div>
8
+ <div class="service-summary__info-wrapper service-summary__info-wrapper--align-center">
9
+ <div class="eyebrow-text large">Subtitle for Locs Installation</div>
10
+ <h2 id="v-0-0-0" class="hero-text mb-20 title axis-horizontal">
11
+ <!--v-if--><span class="text-block-0 normal">Locs Installation</span>
12
+ </h2>
13
+ <div class="service-summary__pills"><span class="display-pill md neutral"><span class="pill-label">60 mins</span></span><span class="display-pill md neutral"><span class="pill-label">From £50</span></span></div>
14
+ <p class="page-body-normal">What this service is.</p>
15
+ </div>
16
+ </div>
17
+ </div>
18
+ <div class="service-summary">
19
+ <div class="service-summary__grid">
20
+ <div class="service-summary__image-wrapper"><img data-nuxt-img="" srcset="/_ipx/_/images/locs-retwist.jpg 1x, /_ipx/_/images/locs-retwist.jpg 2x" alt="Locs Retwist" loading="eager" fetchpriority="auto" class="service-summary__image" src="/_ipx/_/images/locs-retwist.jpg"></div>
21
+ <div class="service-summary__info-wrapper service-summary__info-wrapper--align-center">
22
+ <div class="eyebrow-text large">Subtitle for Locs Retwist</div>
23
+ <h2 id="v-0-0-1" class="hero-text mb-20 title axis-horizontal">
24
+ <!--v-if--><span class="text-block-0 normal">Locs Retwist</span>
25
+ </h2>
26
+ <div class="service-summary__pills"><span class="display-pill md neutral"><span class="pill-label">60 mins</span></span><span class="display-pill md neutral"><span class="pill-label">From £50</span></span></div>
27
+ <p class="page-body-normal">What this service is.</p>
28
+ </div>
29
+ </div>
30
+ </div>
31
+ <div class="service-summary">
32
+ <div class="service-summary__grid">
33
+ <div class="service-summary__image-wrapper"><img data-nuxt-img="" srcset="/_ipx/_/images/colour-treatment.jpg 1x, /_ipx/_/images/colour-treatment.jpg 2x" alt="Colour Treatment" loading="lazy" fetchpriority="auto" class="service-summary__image" src="/_ipx/_/images/colour-treatment.jpg"></div>
34
+ <div class="service-summary__info-wrapper service-summary__info-wrapper--align-center">
35
+ <div class="eyebrow-text large">Subtitle for Colour Treatment</div>
36
+ <h2 id="v-0-0-2" class="hero-text mb-20 title axis-horizontal">
37
+ <!--v-if--><span class="text-block-0 normal">Colour Treatment</span>
38
+ </h2>
39
+ <div class="service-summary__pills"><span class="display-pill md neutral"><span class="pill-label">60 mins</span></span><span class="display-pill md neutral"><span class="pill-label">From £50</span></span></div>
40
+ <p class="page-body-normal">What this service is.</p>
41
+ </div>
42
+ </div>
43
+ </div>
44
+ </div>"
45
+ `;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "srcdev-nuxt-components",
3
3
  "type": "module",
4
- "version": "9.3.10",
4
+ "version": "9.3.12",
5
5
  "main": "nuxt.config.ts",
6
6
  "types": "types.d.ts",
7
7
  "license": "MIT",