Skip to content

Commit

Permalink
Use directive to trigger search with ctrl + enter shortcut in all sea…
Browse files Browse the repository at this point in the history
…rches (#2446)

* General ctrl + enter solution using directives

RISDEV-5819
  • Loading branch information
HPrinz authored Dec 23, 2024
1 parent 7f3ee4d commit ec2de90
Show file tree
Hide file tree
Showing 19 changed files with 180 additions and 29 deletions.
2 changes: 1 addition & 1 deletion frontend/src/components/ActiveCitationInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ onMounted(() => {
</script>

<template>
<div class="flex flex-col gap-24">
<div v-ctrl-enter="search" class="flex flex-col gap-24">
<InputField
id="activeCitationPredicate"
v-slot="slotProps"
Expand Down
23 changes: 6 additions & 17 deletions frontend/src/components/DocumentUnitSearchEntryForm.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts" setup>
import { computed, onMounted, onUnmounted, ref, watch } from "vue"
import { computed, ref, watch } from "vue"
import Checkbox from "@/components/input/CheckboxInput.vue"
import DateInput from "@/components/input/DateInput.vue"
import DropdownInput from "@/components/input/DropdownInput.vue"
Expand Down Expand Up @@ -183,11 +183,6 @@ function handleSearchButtonClicked() {
pushQueryToRoute(query.value)
}
function handleSearchShortcut(event: KeyboardEvent) {
if (event.key == "Enter" && (event.ctrlKey || event.metaKey))
handleSearchButtonClicked()
}
function handleSearch() {
if (!isEmptySearch.value) {
emit("search", getQueryFromRoute())
Expand All @@ -201,17 +196,8 @@ watch(
() => {
handleSearch()
},
{ deep: true },
{ deep: true, immediate: true },
)
onMounted(async () => {
handleSearch()
window.addEventListener("keydown", handleSearchShortcut)
})
onUnmounted(() => {
window.removeEventListener("keydown", handleSearchShortcut)
})
</script>

<script lang="ts">
Expand All @@ -230,7 +216,10 @@ export type DocumentUnitSearchParameter =
</script>
<template>
<div class="pyb-24 mb-32 flex flex-col bg-blue-200">
<div
v-ctrl-enter="handleSearchButtonClicked"
class="pyb-24 mb-32 flex flex-col bg-blue-200"
>
<div
class="m-40 grid grid-flow-col grid-cols-[auto_1fr_auto_1fr] grid-rows-[auto_auto_auto_auto_auto] gap-x-12 gap-y-20 lg:gap-x-32"
>
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/EnsuingDecisionInputGroup.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts" setup>
import { watch, ref, computed, onMounted } from "vue"
import { computed, onMounted, ref, watch } from "vue"
import { ValidationError } from "./input/types"
import SearchResultList, { SearchResults } from "./SearchResultList.vue"
import ComboboxInput from "@/components/ComboboxInput.vue"
Expand Down Expand Up @@ -184,7 +184,7 @@ onMounted(() => {
</script>

<template>
<div class="flex flex-col gap-24">
<div v-ctrl-enter="search" class="flex flex-col gap-24">
<div class="flex flex-col gap-24">
<InputField
id="isPending"
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/PreviousDecisionInputGroup.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts" setup>
import { watch, ref, computed, onMounted } from "vue"
import { computed, onMounted, ref, watch } from "vue"
import { ValidationError } from "./input/types"
import SearchResultList, { SearchResults } from "./SearchResultList.vue"
import ComboboxInput from "@/components/ComboboxInput.vue"
Expand Down Expand Up @@ -179,7 +179,7 @@ onMounted(async () => {
</script>

<template>
<div class="flex flex-col gap-24">
<div v-ctrl-enter="search" class="flex flex-col gap-24">
<div class="flex flex-col gap-24">
<InputField
id="dateKnown"
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/input/TextButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ const render = () => {
</script>

<template>
<div class="w-max" data-testid>
<div class="w-full" data-testid>
<render />
</div>
</template>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,11 @@ onMounted(async () => {
</script>

<template>
<div ref="containerRef" class="flex flex-col border-b-1">
<div
ref="containerRef"
v-ctrl-enter="search"
class="flex flex-col border-b-1"
>
<PopupModal
v-if="showModal"
aria-label="Dialog zur Auswahl der Löschaktion"
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import App from "./App.vue"
import router from "./router"
import useSessionStore from "./stores/sessionStore"
import { filterConsoleWarnings } from "@/utils/filterConsoleWarnings"
import { onSearchShortcutDirective } from "@/utils/onSearchShortcutDirective"

filterConsoleWarnings()

const app = createApp(App)
app.directive("ctrl-enter", onSearchShortcutDirective)
app.use(createHead())

function targets(): string[] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ const handleKeyDown = (event: KeyboardEvent) => {
}
switch (event.key) {
case "<": // Ctrl + [
case "<":
toggleNavigationPanel()
break
case "x":
Expand Down
22 changes: 22 additions & 0 deletions frontend/src/utils/onSearchShortcutDirective.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Directive } from "vue"

export const onSearchShortcutDirective: Directive = {
mounted(el, binding) {
const handleKeydown = (event: KeyboardEvent) => {
if (
el.contains(document.activeElement) && // Ensure focus is inside the element
event.key === "Enter" &&
(event.ctrlKey || event.metaKey)
) {
binding.value?.(event) // Call the provided callback
}
}

el.__handleKeydown__ = handleKeydown // Store reference for cleanup
window.addEventListener("keydown", handleKeydown)
},
unmounted(el) {
window.removeEventListener("keydown", el.__handleKeydown__)
delete el.__handleKeydown__
},
}
21 changes: 20 additions & 1 deletion frontend/test/components/activeCitations.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { CitationType } from "@/domain/citationType"
import DocumentUnit, { Court, DocumentType } from "@/domain/documentUnit"
import documentUnitService from "@/services/documentUnitService"
import featureToggleService from "@/services/featureToggleService"
import { onSearchShortcutDirective } from "@/utils/onSearchShortcutDirective"
import routes from "~/test-helper/routes"

const server = setupServer(
Expand Down Expand Up @@ -49,6 +50,7 @@ function renderComponent(activeCitations?: ActiveCitation[]) {
user,
...render(ActiveCitations, {
global: {
directives: { "ctrl-enter": onSearchShortcutDirective },
plugins: [
[
createTestingPinia({
Expand All @@ -67,7 +69,11 @@ function renderComponent(activeCitations?: ActiveCitation[]) {
],
[router],
],
stubs: { routerLink: { template: "<a><slot/></a>" } },
stubs: {
routerLink: {
template: "<a><slot/></a>",
},
},
},
}),
}
Expand Down Expand Up @@ -389,6 +395,19 @@ describe("active citations", () => {
expect(screen.getAllByText(/test fileNumber/).length).toBe(1)
})

it("search is triggered with shortcut", async () => {
const { user } = renderComponent()

expect(screen.queryByText(/test fileNumber/)).not.toBeInTheDocument()
await user.type(
await screen.findByLabelText("Aktenzeichen Aktivzitierung"),
"test",
)
await user.keyboard("{Control>}{Enter}")

expect(screen.getAllByText(/test fileNumber/).length).toBe(1)
})

it("adds active citation from search results", async () => {
const { user } = renderComponent()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { render, screen } from "@testing-library/vue"
import { createRouter, createWebHistory } from "vue-router"
import DocumentUnitCategories from "@/components/DocumentUnitCategories.vue"
import DocumentUnit from "@/domain/documentUnit"
import { onSearchShortcutDirective } from "@/utils/onSearchShortcutDirective"
import routes from "~/test-helper/routes"

function renderComponent() {
Expand All @@ -17,6 +18,7 @@ function renderComponent() {
user,
...render(DocumentUnitCategories, {
global: {
directives: { "ctrl-enter": onSearchShortcutDirective },
plugins: [
createTestingPinia({
initialState: {
Expand Down
48 changes: 48 additions & 0 deletions frontend/test/components/documentUnit/documentUnitSearch.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Court } from "@/domain/documentUnit"
import DocumentUnitListEntry from "@/domain/documentUnitListEntry"
import authService from "@/services/authService"
import documentUnitService from "@/services/documentUnitService"
import { onSearchShortcutDirective } from "@/utils/onSearchShortcutDirective"
import routes from "~/test-helper/routes"

const server = setupServer(
Expand All @@ -35,6 +36,7 @@ function renderComponent(
user,
...render(DocumentUnitSearch, {
global: {
directives: { "ctrl-enter": onSearchShortcutDirective },
plugins: [
router,
createTestingPinia({
Expand Down Expand Up @@ -253,6 +255,52 @@ describe("Documentunit Search", () => {
expect(screen.getAllByText(/documentNumber/).length).toBe(1)
})

test("Search can be triggered with shortcut", async () => {
vi.spyOn(
documentUnitService,
"searchByDocumentUnitSearchInput",
).mockImplementation(() =>
Promise.resolve({
status: 200,
data: {
content: [
new DocumentUnitListEntry({
uuid: "123",
court: {
type: "type",
location: "location",
label: "type location",
},
decisionDate: "01.02.2022",
documentType: {
jurisShortcut: "documentTypeShortcut",
label: "docTypeLabel",
},
documentNumber: "documentNumber",
fileNumber: "fileNumber",
}),
],
size: 0,
number: 0,
numberOfElements: 20,
first: true,
last: false,
empty: false,
},
}),
)

const { user } = renderComponent()

await user.type(screen.getByLabelText("Gerichtstyp Suche"), "AG")
expect(screen.getByLabelText("Gerichtstyp Suche")).toHaveValue("AG")

await user.keyboard("{Control>}{Enter}")

expect(screen.getAllByRole("row").length).toBe(1)
expect(screen.getAllByText(/documentNumber/).length).toBe(1)
})

test("click on 'Ergebnisse anzeigen' without results let's you create a new doc unit", async () => {
vi.spyOn(
documentUnitService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { render, screen } from "@testing-library/vue"
import { expect } from "vitest"
import { createRouter, createWebHistory } from "vue-router"
import DocumentUnitSearchEntryForm from "@/components/DocumentUnitSearchEntryForm.vue"
import { onSearchShortcutDirective } from "@/utils/onSearchShortcutDirective"

async function renderComponent(options?: { isLoading: boolean }) {
const props = {
Expand All @@ -23,7 +24,12 @@ async function renderComponent(options?: { isLoading: boolean }) {
return {
...render(DocumentUnitSearchEntryForm, {
props,
global: { plugins: [router] },
global: {
directives: {
"ctrl-enter": onSearchShortcutDirective,
},
plugins: [router],
},
}),
user: userEvent.setup(),
router: router,
Expand Down
23 changes: 22 additions & 1 deletion frontend/test/components/ensuingDecisions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import DocumentUnit, { Court, DocumentType } from "@/domain/documentUnit"
import EnsuingDecision from "@/domain/ensuingDecision"
import documentUnitService from "@/services/documentUnitService"
import featureToggleService from "@/services/featureToggleService"
import { onSearchShortcutDirective } from "@/utils/onSearchShortcutDirective"
import routes from "~/test-helper/routes"

const server = setupServer(
Expand Down Expand Up @@ -41,6 +42,9 @@ function renderComponent(ensuingDecisions?: EnsuingDecision[]) {
user,
...render(EnsuingDecisions, {
global: {
directives: {
"ctrl-enter": onSearchShortcutDirective,
},
plugins: [
[
createTestingPinia({
Expand All @@ -57,7 +61,11 @@ function renderComponent(ensuingDecisions?: EnsuingDecision[]) {
],
[router],
],
stubs: { routerLink: { template: "<a><slot/></a>" } },
stubs: {
routerLink: {
template: "<a><slot/></a>",
},
},
},
}),
}
Expand Down Expand Up @@ -326,6 +334,19 @@ describe("EnsuingDecisions", () => {
expect(screen.getAllByText(/test fileNumber/).length).toBe(1)
})

it("search is triggered with shortcut", async () => {
const { user } = renderComponent()

expect(screen.queryByText(/test fileNumber/)).not.toBeInTheDocument()
await user.type(
await screen.findByLabelText("Aktenzeichen Nachgehende Entscheidung"),
"test",
)
await user.keyboard("{Control>}{Enter}")

expect(screen.getAllByText(/test fileNumber/).length).toBe(1)
})

it("adds ensuing decision from search results", async () => {
const { user } = renderComponent()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { createRouter, createWebHistory } from "vue-router"
import PeriodicalEditionReferenceInput from "@/components/periodical-evaluation/references/PeriodicalEditionReferenceInput.vue"
import RelatedDocumentation from "@/domain/relatedDocumentation"
import documentUnitService from "@/services/documentUnitService"
import { onSearchShortcutDirective } from "@/utils/onSearchShortcutDirective"
import routes from "~/test-helper/routes"

function renderComponent() {
Expand All @@ -22,6 +23,7 @@ function renderComponent() {
isSaved: false,
},
global: {
directives: { "ctrl-enter": onSearchShortcutDirective },
plugins: [
router,
[
Expand Down Expand Up @@ -74,6 +76,17 @@ describe("Legal periodical edition reference input", () => {
)
})

it("search is triggered with shortcut", async () => {
vi.spyOn(console, "error").mockImplementation(() => null)
const { user } = renderComponent()

expect(screen.queryByText(/test fileNumber1/)).not.toBeInTheDocument()
await user.type(await screen.findByLabelText("Aktenzeichen"), "test")
await user.keyboard("{Control>}{Enter}")

expect(screen.getAllByText(/test fileNumber1/).length).toBe(1)
})

test("adding a decision scrolls to reference on validation errors", async () => {
vi.spyOn(console, "error").mockImplementation(() => null)

Expand Down
Loading

0 comments on commit ec2de90

Please sign in to comment.