Skip to content

Commit

Permalink
RISDEV-6625 Connect PUT endpoint (#202)
Browse files Browse the repository at this point in the history
  • Loading branch information
andreas-deazevedo authored Feb 14, 2025
1 parent 248561a commit b2468ff
Show file tree
Hide file tree
Showing 11 changed files with 629 additions and 113 deletions.
66 changes: 20 additions & 46 deletions frontend/src/components/DocumentUnitInfoPanel.vue
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
<script lang="ts" setup>
// import dayjs from "dayjs"
// import { computed, ref, toRaw, watchEffect } from "vue"
import { computed, ref, toRaw } from 'vue'
// import { useRoute } from "vue-router"
import { ref, toRaw } from 'vue'
import IconBadge from '@/components/IconBadge.vue'
// import SaveButton from "@/components/SaveDocumentUnitButton.vue"
import { useSaveToRemote } from '@/composables/useSaveToRemote'
import TextButton from '@/components/input/TextButton.vue'
import { useStatusBadge } from '@/composables/useStatusBadge'
import { PublicationState } from '@/domain/publicationStatus'
// import { useDocumentUnitStore } from "@/stores/documentUnitStore"
// import IconError from "~icons/ic/baseline-error"
interface Props {
Expand All @@ -19,43 +15,19 @@ const props = withDefaults(defineProps<Props>(), {
heading: '',
})
// const route = useRoute()
// const documentUnitStore = useDocumentUnitStore()
const fileNumberInfo = computed(
// () => documentUnitStore.documentUnit?.coreData.fileNumbers?.[0] || "",
() => 'Platzhaltertext',
)
const decisionDateInfo = computed(
() => '',
// documentUnitStore.documentUnit?.coreData.decisionDate
// ? dayjs(documentUnitStore.documentUnit.coreData.decisionDate).format(
// "DD.MM.YYYY",
// )
// : "",
)
const courtInfo = computed(
// () => documentUnitStore.documentUnit?.coreData.court?.label || "",
() => '',
)
const formattedInfo = computed(() => {
const parts = [courtInfo.value, fileNumberInfo.value, decisionDateInfo.value].filter(
(part) => part.trim() !== '',
)
return parts.join(', ')
})
const statusBadge = ref(
// useStatusBadge(documentUnitStore.documentUnit?.status).value,
useStatusBadge({
publicationStatus: PublicationState.UNPUBLISHED,
}).value,
)
const formattedInfo = 'Platzhaltertext'
const { saveIsInProgress, triggerSave, lastSaveError, formattedLastSavedOn } = useSaveToRemote()
const getErrorDetails = () => (lastSaveError.value?.title ? ': ' + lastSaveError.value.title : '')
// watchEffect(() => {
// statusBadge.value = useStatusBadge(
// documentUnitStore.documentUnit?.status,
Expand Down Expand Up @@ -92,15 +64,17 @@ const statusBadge = ref(
/> -->

<span class="flex-grow"></span>
<!-- <SaveButton
v-if="
route.path.includes('categories') ||
route.path.includes('attachments') ||
route.path.includes('references')
"
aria-label="Speichern Button"
data-testid="document-unit-save-button"
/> -->
<TextButton data-testid="save-button" label="Speichern" size="small" />
<div class="ml-12 flex items-center space-x-[12px] whitespace-nowrap">
<p v-if="lastSaveError !== undefined" class="ds-label-01-reg text-red-800">
Fehler beim Speichern{{ getErrorDetails() }}
</p>
<p v-else-if="saveIsInProgress === true" class="ds-label-01-reg">speichern...</p>
<p v-else-if="formattedLastSavedOn != undefined" class="ds-label-01-reg">
Zuletzt
<span>{{ formattedLastSavedOn }}</span>
Uhr
</p>
<TextButton data-testid="save-button" label="Speichern" size="small" @click="triggerSave" />
</div>
</div>
</template>
91 changes: 39 additions & 52 deletions frontend/src/components/documentUnitInfoPanel.spec.ts
Original file line number Diff line number Diff line change
@@ -1,72 +1,59 @@
import { describe, it, expect } from 'vitest'
// import { createTestingPinia } from "@pinia/testing"
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { render, screen } from '@testing-library/vue'
// import { createRouter, createWebHistory } from "vue-router"
import DocumentUnitInfoPanel from '@/components/DocumentUnitInfoPanel.vue'
// import DocumentUnit, { type CoreData } from "@/domain/documentUnit"
// import routes from "@/test-helper/routes"
import { useDocumentUnitStore } from '@/stores/documentUnitStore.ts'
import { setActivePinia } from 'pinia'
import { createTestingPinia } from '@pinia/testing'

function mockDocumentUnitStore(callback = vi.fn()) {
const documentUnitStore = useDocumentUnitStore()
documentUnitStore.updateDocumentUnit = callback

return documentUnitStore
}

function renderComponent(options?: { heading?: string /*coreData?: CoreData*/ }) {
// const router = createRouter({
// history: createWebHistory(),
// routes: routes,
// })
return {
...render(DocumentUnitInfoPanel, {
props: { heading: options?.heading ?? '' },
// global: {
// plugins: [
// router,
// createTestingPinia({
// initialState: {
// docunitStore: {
// documentUnit: new DocumentUnit("foo", {
// documentNumber: "1234567891234",
// coreData: options?.coreData ?? {
// court: {
// type: "AG",
// location: "Test",
// label: "AG Test",
// },
// },
// }),
// },
// },
// }),
// ],
// },
}),
}
}

describe('documentUnit InfoPanel', () => {
beforeEach(() => {
setActivePinia(createTestingPinia())
})

it('renders heading if given', async () => {
renderComponent({ heading: 'test heading' })
renderComponent({ heading: 'Header' })

// TODO: is this a proper test? Fix it when the below gets un-commented
expect(await screen.findByText('test heading')).toBeVisible()
expect(await screen.findByText('Header')).toBeVisible()
})

it('click on save renders last saved information', async () => {
// given
mockDocumentUnitStore(vi.fn().mockResolvedValueOnce({ status: 200 }))
renderComponent()

// it("renders all given property infos in correct order", async () => {
// const coreData = {
// decisionDate: "2024-01-01",
// fileNumbers: ["AZ123"],
// court: {
// type: "AG",
// location: "Test",
// label: "AG Test",
// },
// }
// renderComponent({ coreData: coreData })
// when
screen.getByRole('button', { name: 'Speichern' }).click()

// then
expect(await screen.findByText('Zuletzt', { exact: false })).toBeInTheDocument()
})

// expect(
// await screen.findByText("AG Test, AZ123, 01.01.2024"),
// ).toBeInTheDocument()
// })
it('click on save renders error information', async () => {
// given
mockDocumentUnitStore()
renderComponent()

// it("omits incomplete coredata fields from rendering", async () => {
// renderComponent()
// when
screen.getByRole('button', { name: 'Speichern' }).click()

// expect(await screen.findByText("AG Test")).toBeInTheDocument()
// })
// then
expect(
await screen.findByText('Fehler beim Speichern: Verbindung fehlgeschlagen'),
).toBeInTheDocument()
})
})
175 changes: 175 additions & 0 deletions frontend/src/composables/useSaveToRemote.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import { createTestingPinia } from '@pinia/testing'
import { flushPromises } from '@vue/test-utils'
import { setActivePinia } from 'pinia'
import { useSaveToRemote } from '@/composables/useSaveToRemote'
import { useDocumentUnitStore } from '@/stores/documentUnitStore'
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'

vi.mock('vue', async (importActual) => {
const vue: Record<string, unknown> = await importActual()
return { ...vue, onUnmounted: vi.fn() }
})

function mockDocumentUnitStore(callback = vi.fn()) {
const documentUnitStore = useDocumentUnitStore()
documentUnitStore.updateDocumentUnit = callback

return documentUnitStore
}

describe('useSaveToRemote', () => {
beforeEach(() => {
vi.useFakeTimers()
setActivePinia(createTestingPinia())
})

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

it('calls the callback every time the trigger is called', async () => {
const documentUnitStore = mockDocumentUnitStore()
const { triggerSave } = useSaveToRemote()

await triggerSave()
expect(documentUnitStore.updateDocumentUnit).toHaveBeenCalledTimes(1)

await triggerSave()
expect(documentUnitStore.updateDocumentUnit).toHaveBeenCalledTimes(2)
})

it('does not call the callback if a call is still in progress', async () => {
let resolveCallback: (data: unknown) => void = vi.fn()
const callback = vi
.fn()
.mockImplementation(() => new Promise((resolve) => (resolveCallback = resolve)))

const documentUnitStore = mockDocumentUnitStore(callback)
const { triggerSave } = useSaveToRemote()

triggerSave().then(() => {})
triggerSave().then(() => {})
resolveCallback(undefined)
await flushPromises()
triggerSave().then(() => {})

expect(documentUnitStore.updateDocumentUnit).toHaveBeenCalledTimes(2)
})

it('toggles the in progress state while callback runs', async () => {
let resolveCallback: (data: unknown) => void = vi.fn()
const callback = vi
.fn()
.mockImplementation(() => new Promise((resolve) => (resolveCallback = resolve)))

mockDocumentUnitStore(callback)
const { triggerSave, saveIsInProgress } = useSaveToRemote()

expect(saveIsInProgress.value).toBe(false)

triggerSave().then(() => {})

expect(saveIsInProgress.value).toBe(true)

resolveCallback(undefined)
await flushPromises()

expect(saveIsInProgress.value).toBe(false)
})

it('also sets back the in progress state when callback throws exception', async () => {
const callback = vi.fn().mockRejectedValue(new Error())
mockDocumentUnitStore(callback)
const { triggerSave, saveIsInProgress } = useSaveToRemote()

await triggerSave()

expect(saveIsInProgress.value).toBe(false)
})

it('sets the response error if callback returns one', async () => {
const callback = vi.fn().mockResolvedValue({ status: 400, error: { title: 'error' } })
mockDocumentUnitStore(callback)
const { triggerSave, lastSaveError } = useSaveToRemote()

await triggerSave()

expect(lastSaveError.value).toEqual({ title: 'error' })
})

it('sets connection error if callback throws exception one', async () => {
const callback = vi.fn().mockRejectedValue(new Error())
mockDocumentUnitStore(callback)
const { triggerSave, lastSaveError } = useSaveToRemote()

await triggerSave()

expect(lastSaveError.value).toEqual({ title: 'Verbindung fehlgeschlagen' })
})

it('resets the response error after the next successful save', async () => {
mockDocumentUnitStore()
const { triggerSave, lastSaveError } = useSaveToRemote()

expect(lastSaveError.value).toBeUndefined()

mockDocumentUnitStore(vi.fn().mockResolvedValueOnce({ status: 400, error: { title: 'error' } }))

await triggerSave()

expect(lastSaveError.value).toBeDefined()

mockDocumentUnitStore(vi.fn().mockResolvedValueOnce({ status: 200, data: undefined }))
await triggerSave()

expect(lastSaveError.value).toBeUndefined()
})

it('sets the last save on date only after each successfully callback call', async () => {
mockDocumentUnitStore()
const { triggerSave, formattedLastSavedOn } = useSaveToRemote()

expect(formattedLastSavedOn.value).toBeUndefined()

vi.setSystemTime(60_000)
mockDocumentUnitStore(vi.fn().mockResolvedValueOnce({ status: 200, data: undefined }))
await triggerSave()

const firstLastSavedOn = formattedLastSavedOn.value
expect(firstLastSavedOn).toBeDefined()

vi.setSystemTime(120_000)
mockDocumentUnitStore(vi.fn().mockResolvedValueOnce({ status: 400, error: { title: 'error' } }))
await triggerSave()

expect(formattedLastSavedOn.value).toBe(firstLastSavedOn)

vi.setSystemTime(180_000)
mockDocumentUnitStore(vi.fn().mockResolvedValueOnce({ status: 200, data: undefined }))
await triggerSave()

expect(formattedLastSavedOn.value).not.toBe(firstLastSavedOn)
})

it('does not reset error if callback did not change anything', async () => {
mockDocumentUnitStore()
const { triggerSave, formattedLastSavedOn, lastSaveError } = useSaveToRemote()

mockDocumentUnitStore(
vi
.fn()
.mockResolvedValueOnce({ status: 400, error: { title: 'error' } })
.mockResolvedValueOnce({ status: 304, data: undefined }),
)

// first save attempt with error response
await triggerSave()
expect(formattedLastSavedOn.value).toBeUndefined()
expect(lastSaveError.value).toEqual({ title: 'error' })

// second save attepmpt, nothing changed
await triggerSave()
expect(formattedLastSavedOn.value).toBeUndefined()
expect(lastSaveError.value).toEqual({ title: 'error' })
})
})
Loading

0 comments on commit b2468ff

Please sign in to comment.