|
1 | 1 | import type { Meta, StoryObj } from "@storybook/vue3-vite"; |
| 2 | +import { expect, userEvent, within } from "storybook/test"; |
2 | 3 | import { ref } from "vue"; |
3 | 4 | import RRating from "./RRating.vue"; |
4 | 5 |
|
@@ -39,6 +40,38 @@ export const Default: Story = { args: { halfIncrements: true, hover: true } }; |
39 | 40 | export const Readonly: Story = { args: { readonly: true } }; |
40 | 41 | export const Large: Story = { args: { size: "large" } }; |
41 | 42 |
|
| 43 | +// Keyboard: Tab across the star buttons, Enter/Space commits the rating. |
| 44 | +export const KeyboardNav: Story = { |
| 45 | + name: "Keyboard navigation (play)", |
| 46 | + args: { ariaLabel: "Rating" }, |
| 47 | + render: (args) => ({ |
| 48 | + components: { RRating }, |
| 49 | + setup: () => { |
| 50 | + const value = ref(0); |
| 51 | + return { args, value }; |
| 52 | + }, |
| 53 | + template: `<RRating v-bind="args" v-model="value" />`, |
| 54 | + }), |
| 55 | + play: async ({ canvasElement, step }) => { |
| 56 | + const canvas = within(canvasElement); |
| 57 | + const stars = canvas.getAllByRole("radio"); |
| 58 | + |
| 59 | + await step("Tab focuses the stars in order", async () => { |
| 60 | + await userEvent.tab(); |
| 61 | + expect(stars[0]).toHaveFocus(); |
| 62 | + await userEvent.tab(); |
| 63 | + await userEvent.tab(); |
| 64 | + expect(stars[2]).toHaveFocus(); |
| 65 | + }); |
| 66 | + |
| 67 | + await step("Enter commits the focused star as the rating", async () => { |
| 68 | + await userEvent.keyboard("{Enter}"); |
| 69 | + expect(stars[2]).toHaveAttribute("aria-checked", "true"); |
| 70 | + expect(stars[0]).toHaveAttribute("aria-checked", "false"); |
| 71 | + }); |
| 72 | + }, |
| 73 | +}; |
| 74 | + |
42 | 75 | // Difficulty preset — same primitive driven by props. Exercises the |
43 | 76 | // new emptyIcon/fullIcon/activeColor pass-through used by the |
44 | 77 | // score-picker on GameDetails. |
|
0 commit comments