Skip to content

Commit 345d4ee

Browse files
authored
feat(unify): add number of matches to specs search (#19076)
* move event manager functionality into props, remove tests for studio button * wip * add count to specs list search * accessibility and test tweaks
1 parent 168600b commit 345d4ee

File tree

8 files changed

+92
-14
lines changed

8 files changed

+92
-14
lines changed

packages/app/src/specs/InlineSpecList.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
<div class="w-280px">
33
<InlineSpecListHeader
44
v-model:search="search"
5+
:result-count="specs.length"
56
/>
67
<div class="h-[calc(100vh-65px)] overflow-y-auto overflow-x-hidden pt-16px">
78
<InlineSpecListTree

packages/app/src/specs/InlineSpecListHeader.spec.tsx

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import InlineSpecListHeader from './InlineSpecListHeader.vue'
22
import { ref } from 'vue'
3+
import { defaultMessages } from '@cy/i18n'
34

45
describe('InlineSpecListHeader', () => {
5-
beforeEach(() => {
6+
const mountWithResultCount = (resultCount = 0) => {
67
const search = ref('')
78
const onAddSpec = cy.spy().as('new-spec')
89

@@ -18,21 +19,36 @@ describe('InlineSpecListHeader', () => {
1819

1920
cy.mount(() =>
2021
(<div class="bg-gray-1000">
21-
<InlineSpecListHeader {...methods} />
22+
<InlineSpecListHeader {...methods} resultCount={resultCount} />
2223
</div>))
23-
})
24+
}
2425

2526
it('should allow search', () => {
27+
mountWithResultCount(0)
2628
const searchString = 'my/component.cy.tsx'
2729

28-
cy.get('input')
30+
cy.findByLabelText(defaultMessages.specPage.searchPlaceholder)
2931
.type(searchString, { delay: 0 })
3032
.get('@search').its('value').should('eq', searchString)
3133
})
3234

3335
it('should emit add spec', () => {
34-
cy.get('[data-cy="runner-spec-list-add-spec"]').click()
36+
mountWithResultCount(0)
37+
cy.findAllByLabelText(defaultMessages.specPage.newSpecButton)
38+
.click()
3539
.get('@new-spec')
3640
.should('have.been.called')
3741
})
42+
43+
it('exposes the result count correctly to assistive tech', () => {
44+
mountWithResultCount(0)
45+
cy.contains(`0 ${ defaultMessages.specPage.matchPlural}`)
46+
.should('have.class', 'sr-only')
47+
.and('have.attr', 'aria-live', 'polite')
48+
49+
mountWithResultCount(1)
50+
cy.contains(`1 ${ defaultMessages.specPage.matchSingular}`).should('have.class', 'sr-only')
51+
mountWithResultCount(100)
52+
cy.contains(`100 ${ defaultMessages.specPage.matchPlural}`).should('have.class', 'sr-only')
53+
})
3854
})

packages/app/src/specs/InlineSpecListHeader.vue

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
/>
99
</div>
1010
<input
11+
id="inline-spec-list-header-search"
1112
class="
1213
w-full
1314
bg-gray-1000
@@ -17,14 +18,22 @@
1718
font-light
1819
outline-none
1920
"
20-
placeholder="Search Specs"
21-
:value="search"
21+
:value="props.search"
22+
minlength="1"
2223
@focus="inputFocused = true"
2324
@blur="inputFocused = false"
2425
@input="onInput"
2526
>
27+
<label
28+
for="inline-spec-list-header-search"
29+
class="search-label absolute left-24px text-gray-300 pointer-events-none font-light"
30+
:class="{
31+
'opacity-0': inputFocused || props.search.length
32+
}"
33+
>
34+
{{ t('specPage.searchPlaceholder') }}
35+
</label>
2636
</div>
27-
2837
<button
2938
class="
3039
border-1 border-gray-900
@@ -37,21 +46,30 @@
3746
items-center
3847
justify-center
3948
"
40-
data-cy="runner-spec-list-add-spec"
49+
:aria-label="t('specPage.newSpecButton')"
4150
@click="emit('addSpec')"
4251
>
4352
<i-cy-add-small_x16 class="icon-light-gray-50 icon-dark-gray-200" />
4453
</button>
54+
<div
55+
class="sr-only"
56+
aria-live="polite"
57+
>
58+
{{ resultCount }} {{ resultCount === 1 ? t('specPage.matchSingular') : t('specPage.matchPlural') }}
59+
</div>
4560
</div>
4661
</template>
4762

4863
<script lang="ts" setup>
4964
import Input from '@cy/components/Input.vue'
5065
import Button from '@cy/components/Button.vue'
5166
import { ref } from 'vue'
67+
import { useI18n } from '@cy/i18n'
5268
53-
defineProps<{
69+
const { t } = useI18n()
70+
const props = defineProps<{
5471
search: string
72+
resultCount: number
5573
}>()
5674
5775
const emit = defineEmits<{

packages/app/src/specs/SpecsList.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<SpecsListHeader
1010
v-model="search"
1111
class="pb-32px"
12+
:result-count="specs.length"
1213
@newSpec="showModal = true"
1314
/>
1415

packages/app/src/specs/SpecsListHeader.spec.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import SpecsListHeader from './SpecsListHeader.vue'
22
import { defineComponent, ref, h } from 'vue'
3+
import { defaultMessages } from '@cy/i18n'
34

45
const buttonSelector = '[data-testid=new-spec-button]'
56
const inputSelector = 'input[type=search]'
@@ -33,10 +34,30 @@ describe('<SpecsListHeader />', { keystrokeDelay: 0 }, () => {
3334
cy.mount(() => (<div class="max-w-800px p-12 resize overflow-auto"><SpecsListHeader
3435
modelValue={search.value}
3536
onNewSpec={onNewSpec}
37+
resultCount={0}
3638
/></div>))
3739
.get(buttonSelector)
3840
.click()
3941
.get('@new-spec')
4042
.should('have.been.called')
4143
})
44+
45+
it('shows the result count correctly', () => {
46+
const mountWithResultCount = (count = 0) => {
47+
cy.mount(() => (<div class="max-w-800px p-12 resize overflow-auto"><SpecsListHeader
48+
modelValue={''}
49+
resultCount={count}
50+
/></div>))
51+
}
52+
53+
mountWithResultCount(0)
54+
cy.contains(`0 ${ defaultMessages.specPage.matchPlural}`)
55+
.should('be.visible')
56+
.and('have.attr', 'aria-live', 'polite')
57+
58+
mountWithResultCount(1)
59+
cy.contains(`1 ${ defaultMessages.specPage.matchSingular}`).should('be.visible')
60+
mountWithResultCount(100)
61+
cy.contains(`100 ${ defaultMessages.specPage.matchPlural}`).should('be.visible')
62+
})
4263
})

packages/app/src/specs/SpecsListHeader.vue

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<div class="flex w-full gap-16px">
2+
<div class="flex w-full gap-16px relative">
33
<Input
44
type="search"
55
class="flex-grow h-full min-w-200px"
@@ -8,7 +8,16 @@
88
:model-value="props.modelValue"
99
:placeholder="t('specPage.searchPlaceholder')"
1010
@input="onInput"
11-
/>
11+
>
12+
<template #suffix>
13+
<div
14+
class="text-gray-500 border-l border-l-gray-100 pl-16px"
15+
aria-live="polite"
16+
>
17+
{{ resultCount }} {{ resultCount === 1 ? t('specPage.matchSingular') : t('specPage.matchPlural') }}
18+
</div>
19+
</template>
20+
</Input>
1221

1322
<div class="flex h-40px gap-16px min-w-127px">
1423
<Button
@@ -34,9 +43,12 @@ import IconAdd from '~icons/cy/add-large_x16'
3443
3544
const { t } = useI18n()
3645
37-
const props = defineProps<{
46+
const props = withDefaults(defineProps<{
3847
modelValue: string
39-
}>()
48+
resultCount?: number
49+
}>(), {
50+
resultCount: 0,
51+
})
4052
4153
const emit = defineEmits<{
4254
(e: 'update:modelValue', value: string): void,

packages/frontend-shared/src/components/Input.vue

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,10 @@ const _inputClasses = computed(() => {
113113
})
114114
115115
</script>
116+
117+
<style scoped>
118+
::-webkit-search-cancel-button{
119+
display: none;
120+
}
121+
122+
</style>

packages/frontend-shared/src/locales/en-US.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@
9292
"pageTitle": "Specs",
9393
"newSpecButton": "New Spec",
9494
"searchPlaceholder": "Search Specs",
95+
"matchPlural": "Matches",
96+
"matchSingular": "Match",
9597
"componentSpecsHeader": "Component Specs",
9698
"e2eSpecsHeader": "E2E Specs",
9799
"gitStatusHeader": "Git Status",

0 commit comments

Comments
 (0)