From d9fc3d92e4f4e4a9d5446a188e26891c83af2c25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Wed, 13 May 2026 00:28:39 +0300 Subject: [PATCH] =?UTF-8?q?fix(a11y):=20Q.DEFER.004=20sub-A=20=E2=80=94=20?= =?UTF-8?q?DealsTable=20show-select=20bulk-checkbox=20aria-label?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VDataTable show-select prop генерировал unlabeled checkbox per row + select-all header — axe-core критичная label violation (6 nodes на demo seed). Override через Vuetify 3.12 typed slots: - header.data-table-select → aria-label='Выбрать все сделки' - item.data-table-select → aria-label='Выбрать сделку «{{name}}»' (per row) Test coverage: tests/Frontend/DealsTable.spec.ts (2 specs). --- .../js/components/deals/DealsTable.vue | 17 ++++++ app/tests/Frontend/DealsTable.spec.ts | 59 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 app/tests/Frontend/DealsTable.spec.ts diff --git a/app/resources/js/components/deals/DealsTable.vue b/app/resources/js/components/deals/DealsTable.vue index 1c3a9619..27afbf0d 100644 --- a/app/resources/js/components/deals/DealsTable.vue +++ b/app/resources/js/components/deals/DealsTable.vue @@ -122,6 +122,23 @@ function formatCost(cost: number): string { + + + +
diff --git a/app/tests/Frontend/DealsTable.spec.ts b/app/tests/Frontend/DealsTable.spec.ts new file mode 100644 index 00000000..cda6baff --- /dev/null +++ b/app/tests/Frontend/DealsTable.spec.ts @@ -0,0 +1,59 @@ +import { describe, it, expect } from 'vitest'; +import { mount } from '@vue/test-utils'; +import { createVuetify } from 'vuetify'; + +import DealsTable from '../../resources/js/components/deals/DealsTable.vue'; +import type { MockDeal } from '../../resources/js/composables/mockDeals'; + +const vuetify = createVuetify(); + +const sampleDeals: MockDeal[] = [ + { + id: 1, + name: 'Иванов И.', + phone: '+7 (916) 100-00-01', + statusSlug: 'new', + project: 'B1 site', + manager: { initials: 'AD', name: 'Admin' }, + cost: 1000, + receivedMinutesAgo: 5, + }, + { + id: 2, + name: 'Петров П.', + phone: '+7 (916) 100-00-02', + statusSlug: 'new', + project: 'B1 call', + manager: { initials: 'AD', name: 'Admin' }, + cost: 1500, + receivedMinutesAgo: 30, + }, +]; + +describe('DealsTable a11y (Q.DEFER.004 sub-A)', () => { + it('select-all header checkbox has aria-label', () => { + const wrapper = mount(DealsTable, { + props: { deals: sampleDeals, selectedIds: [], statusBySlug: new Map() }, + global: { plugins: [vuetify] }, + }); + const headerCheckbox = wrapper.find( + 'th .v-selection-control input[type="checkbox"][aria-label="Выбрать все сделки"]', + ); + expect(headerCheckbox.exists()).toBe(true); + }); + + it('each row checkbox has aria-label referencing deal name', () => { + const wrapper = mount(DealsTable, { + props: { deals: sampleDeals, selectedIds: [], statusBySlug: new Map() }, + global: { plugins: [vuetify] }, + }); + const rowCheckbox1 = wrapper.find( + 'tbody tr:nth-of-type(1) input[type="checkbox"][aria-label="Выбрать сделку «Иванов И.»"]', + ); + const rowCheckbox2 = wrapper.find( + 'tbody tr:nth-of-type(2) input[type="checkbox"][aria-label="Выбрать сделку «Петров П.»"]', + ); + expect(rowCheckbox1.exists()).toBe(true); + expect(rowCheckbox2.exists()).toBe(true); + }); +});