Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions components/FullPageTabs/FullPageTabs.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<CdataLink
class="!bg-none bg-blue-action-low px-4 py-2 aria-current-page:text-new-primary aria-current-page:border-t-2 aria-current-page:border-t-new-primary aria-current-page:bg-white aria-current-page:border-x aria-current-page:border-x-gray-default hover:aria-current-page:!bg-white active:aria-current-page:!bg-white hover:!bg-blue-action-low-hover active:!bg-blue-action-low-active"
:to="link.href"
:aria-current="isCurrentUrl(link.href) ? 'page': false"
:aria-current="isCurrentTab(link.href) ? 'page': false"
>
{{ link.label }}
<sup
Expand All @@ -31,12 +31,12 @@
</template>

<script setup lang="ts">
withDefaults(defineProps<{
const props = withDefaults(defineProps<{
links: Array<{ href: string, label: string, count?: number }>
as?: string
}>(), {
as: 'h2',
})

const isCurrentUrl = useIsCurrentUrl()
const isCurrentTab = useIsCurrentTab(() => props.links)
</script>
6 changes: 3 additions & 3 deletions components/TabLinks.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
v-if="link.show === undefined ? true : link.show"
:to="link.href"
class="whitespace-nowrap md:whitespace-normal group block rounded bg-none bg-transparent border border-transparent -m-px no-underline outline-none aria-current-page:border aria-current-page:border-new-primary aria-current-page:text-new-primary p-1"
:aria-current="isCurrentUrl(link.href) ? 'page': false"
:aria-current="isCurrentTab(link.href) ? 'page': false"
>
<span class="rounded px-2">
{{ link.label }}
Expand All @@ -21,11 +21,11 @@
</template>

<script setup lang="ts">
defineProps<{
const props = defineProps<{
links: Array<{ href: string, label: string, show?: boolean }>
}>()

const isCurrentUrl = useIsCurrentUrl()
const isCurrentTab = useIsCurrentTab(() => props.links)
</script>

<style scoped>
Expand Down
65 changes: 64 additions & 1 deletion tests-unit/utils/helpers.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { humanJoin, redirectLegacyHashes, removeLangPrefix } from '~/utils/helpers'
import { createMemoryHistory, createRouter } from 'vue-router'
import { humanJoin, redirectLegacyHashes, removeLangPrefix, useIsCurrentTab } from '~/utils/helpers'

describe('removeLangPrefix', () => {
it('removes a language prefix', () => {
Expand Down Expand Up @@ -32,6 +33,68 @@ describe('humanJoin', () => {
})
})

describe('useIsCurrentTab', () => {
const router = createRouter({
history: createMemoryHistory(),
routes: [{ path: '/:pathMatch(.*)*', component: {} }],
})

const setup = (fullPath: string, links: Array<{ href: string }>) => {
vi.stubGlobal('useRouter', () => router)
vi.stubGlobal('useRoute', () => router.resolve(fullPath))
vi.stubGlobal('useRequestURL', () => new URL('https://www.data.gouv.fr/'))
vi.stubGlobal('useRuntimeConfig', () => ({ public: { apiBase: 'https://www.data.gouv.fr' } }))
return useIsCurrentTab(links)
}

afterEach(() => {
vi.unstubAllGlobals()
})

const datasetTabs = [
{ href: '/datasets/slug' },
{ href: '/datasets/slug/discussions' },
]

it('selects the tab matching the current path', () => {
let isCurrentTab = setup('/datasets/slug', datasetTabs)
expect(isCurrentTab('/datasets/slug')).toBe(true)
expect(isCurrentTab('/datasets/slug/discussions')).toBe(false)

isCurrentTab = setup('/datasets/slug/discussions', datasetTabs)
expect(isCurrentTab('/datasets/slug')).toBe(false)
expect(isCurrentTab('/datasets/slug/discussions')).toBe(true)
})

it('keeps the tab selected when the page adds its own query params', () => {
const isCurrentTab = setup('/datasets/slug?resource_id=abc-123&page=2', datasetTabs)
expect(isCurrentTab('/datasets/slug')).toBe(true)
expect(isCurrentTab('/datasets/slug/discussions')).toBe(false)
})

it('still discriminates on the query params the tabs themselves set', () => {
const moderationTabs = [
{ href: '/admin/site/moderation' },
{ href: '/admin/site/moderation?type=Dataset' },
{ href: '/admin/site/moderation?type=Reuse' },
]

let isCurrentTab = setup('/admin/site/moderation?type=Dataset&page=3', moderationTabs)
expect(isCurrentTab('/admin/site/moderation')).toBe(false)
expect(isCurrentTab('/admin/site/moderation?type=Dataset')).toBe(true)
expect(isCurrentTab('/admin/site/moderation?type=Reuse')).toBe(false)

isCurrentTab = setup('/admin/site/moderation?page=3', moderationTabs)
expect(isCurrentTab('/admin/site/moderation')).toBe(true)
expect(isCurrentTab('/admin/site/moderation?type=Dataset')).toBe(false)
})

it('accepts absolute urls and trailing slashes', () => {
const isCurrentTab = setup('/datasets/slug?resource_id=abc-123', datasetTabs)
expect(isCurrentTab('https://www.data.gouv.fr/datasets/slug/')).toBe(true)
})
})

describe('redirectLegacyHashes', () => {
const instructions = [
{ from: 'discussions', to: '/datasets/slug/discussions', queryParam: 'discussion_id' },
Expand Down
25 changes: 25 additions & 0 deletions utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,31 @@ export function useIsCurrentUrl() {
}
}

/**
* Tells which link of a group of tabs is the current one.
*
* Unlike `useIsCurrentUrl`, only the query params that some tab actually sets are
* compared: any other param (a selected resource, a page number, a filter…) belongs
* to the page, not to the tab group, and must not deselect the current tab.
*/
export function useIsCurrentTab(links: MaybeRefOrGetter<Array<{ href: string }>>) {
const absoluteUrlToRelative = useAbsoluteUrlToRelative()
const router = useRouter()
const route = useRoute()

const resolve = (url: string) => router.resolve(absoluteUrlToRelative(url))

const tabParams = computed(() => new Set(toValue(links).flatMap(link => Object.keys(resolve(link.href).query))))

return (url: string): boolean => {
const link = resolve(url)

if (trimEndSlash(link.path) !== trimEndSlash(route.path)) return false

return [...tabParams.value].every(param => link.query[param] === route.query[param])
}
}

export function humanJoin(source: Array<string>): string {
const array = [...source]

Expand Down
Loading