feat: Update user authentication data and enhance routine item handling in RoutineEditView
All checks were successful
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 3m16s

This commit is contained in:
2026-05-18 16:39:05 -04:00
parent 5392e5af70
commit ad8a8bf867
5 changed files with 91 additions and 24 deletions

View File

@@ -2,20 +2,20 @@
"cookies": [
{
"name": "refresh_token",
"value": "vB_LRXjnl0ucrR2XdmRzT2hdBCb656qv75SB6s3yuCM",
"value": "oxkpzGInQ4shJIvaKgzPTk-G1g-yuISm-eWLWAHCz8c",
"domain": "localhost",
"path": "/api/auth",
"expires": 1786851645.664007,
"expires": 1786910690.97027,
"httpOnly": true,
"secure": true,
"sameSite": "Strict"
},
{
"name": "access_token",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZS1jY0B0ZXN0LmNvbSIsInVzZXJfaWQiOiI0YjlmNGE2NS0xM2JmLTRkZDEtYTE3Mi1hODFmYzNkNjQ3ZWQiLCJ0b2tlbl92ZXJzaW9uIjowLCJleHAiOjE3NzkwODY0NDV9.hxhTHPEnMJagMq3tUpW5qIon3qgx1u77TikMwrXfM3s",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZS1jY0B0ZXN0LmNvbSIsInVzZXJfaWQiOiIzMDgxMGE1MS0xOGRkLTRjOGEtOWU2MS0xZGRjMGM5NGQ2ODAiLCJ0b2tlbl92ZXJzaW9uIjowLCJleHAiOjE3NzkxNDU0OTB9.wgl1nIrfwav8NhRqXaqglqbtwZ-W6M3FfR2_J00v_lw",
"domain": "localhost",
"path": "/",
"expires": 1779086445.663928,
"expires": 1779145490.969587,
"httpOnly": true,
"secure": true,
"sameSite": "Lax"
@@ -27,11 +27,11 @@
"localStorage": [
{
"name": "authSyncEvent",
"value": "{\"type\":\"logout\",\"at\":1779075645509}"
"value": "{\"type\":\"logout\",\"at\":1779134690681}"
},
{
"name": "parentAuth",
"value": "{\"expiresAt\":1779248445817}"
"value": "{\"expiresAt\":1779307491379}"
}
]
}

View File

@@ -2,20 +2,20 @@
"cookies": [
{
"name": "refresh_token",
"value": "gboIPp5ceYO1Kb0CtJoBNLLEB_aqa9hpmWeQcYU5ts0",
"value": "n_EftQzpLANJ_pI4xwg6LQDCDds9Ufvy1GcnnYJQ00A",
"domain": "localhost",
"path": "/api/auth",
"expires": 1786851645.795109,
"expires": 1786910690.820234,
"httpOnly": true,
"secure": true,
"sameSite": "Strict"
},
{
"name": "access_token",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZS1kZWxldGVAdGVzdC5jb20iLCJ1c2VyX2lkIjoiYzBlNTdhNzktZTU2OS00Y2RmLTk0MDQtYzdiYjI3ZmRkMjAyIiwidG9rZW5fdmVyc2lvbiI6MCwiZXhwIjoxNzc5MDg2NDQ1fQ.WXFDSpITZGB74reIt7-NMFdS40oxrUhqhkQdqDdCrK4",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZS1kZWxldGVAdGVzdC5jb20iLCJ1c2VyX2lkIjoiNDE3OWYwNzItYTM3MC00NGY1LTg1YmMtZmU1ZDIwNzY3NWE3IiwidG9rZW5fdmVyc2lvbiI6MCwiZXhwIjoxNzc5MTQ1NDkwfQ.1uhXDEXNncubgx4D8XnFI6KWxXT4wvvvGrXQfHvjnhU",
"domain": "localhost",
"path": "/",
"expires": 1779086445.79506,
"expires": 1779145490.819506,
"httpOnly": true,
"secure": true,
"sameSite": "Lax"
@@ -27,11 +27,11 @@
"localStorage": [
{
"name": "authSyncEvent",
"value": "{\"type\":\"logout\",\"at\":1779075645659}"
"value": "{\"type\":\"logout\",\"at\":1779134690558}"
},
{
"name": "parentAuth",
"value": "{\"expiresAt\":1779248445953}"
"value": "{\"expiresAt\":1779307491309}"
}
]
}

View File

@@ -2,20 +2,20 @@
"cookies": [
{
"name": "refresh_token",
"value": "vp9tOOruGSijnoaSX6nKSaRCEsSzsiln0Jt1sSaaVtg",
"value": "QCuLIgmPV2vnDfzmgvptt-mZAW1jh8UML_wwAee4OTQ",
"domain": "localhost",
"path": "/api/auth",
"expires": 1786851643.974239,
"expires": 1786912678.702645,
"httpOnly": true,
"secure": true,
"sameSite": "Strict"
},
{
"name": "access_token",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZUB0ZXN0LmNvbSIsInVzZXJfaWQiOiJjYjU5ZWU1Ni1iNDJkLTQ2NDctOTQ0MC1lNzYwYmJlMjg2ZDYiLCJ0b2tlbl92ZXJzaW9uIjowLCJleHAiOjE3NzkwODY0NDN9.8xB5EOIA_PBXC3KwajHcAfpZVC8c78OP0HaExyrYRV8",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZUB0ZXN0LmNvbSIsInVzZXJfaWQiOiJlM2VmNGE2OC0zZWI3LTQ2MjQtYTI0Mi0yNmY1OTgyZDIwOWEiLCJ0b2tlbl92ZXJzaW9uIjowLCJleHAiOjE3NzkxNDc0Nzh9.P7CDplAG3MBXgtEpuYFtp6gOV5pi-6QeAYRqmE-Dkys",
"domain": "localhost",
"path": "/",
"expires": 1779086443.974189,
"expires": 1779147478.701971,
"httpOnly": true,
"secure": true,
"sameSite": "Lax"
@@ -27,11 +27,11 @@
"localStorage": [
{
"name": "authSyncEvent",
"value": "{\"type\":\"logout\",\"at\":1779075643795}"
"value": "{\"type\":\"logout\",\"at\":1779136678495}"
},
{
"name": "parentAuth",
"value": "{\"expiresAt\":1779248444100}"
"value": "{\"expiresAt\":1779309478870}"
}
]
}

View File

@@ -372,12 +372,23 @@ async function handleSubmit(form: { name: string; points: number; image_id: stri
})
if (!resp.ok) throw new Error('Failed to save routine')
const routineData = await resp.json()
const routineId = routineData.id || props.id
const routineId =
props.id ||
(typeof routineData?.id === 'string' && routineData.id) ||
(typeof routineData?.routine?.id === 'string' && routineData.routine.id) ||
''
if (!routineId) {
throw new Error('Failed to resolve routine id after save')
}
for (const itemId of removedItemIds.value) {
await fetch(`/api/routine/${routineId}/item/${itemId}`, {
const deleteResp = await fetch(`/api/routine/${routineId}/item/${itemId}`, {
method: 'DELETE',
})
if (!deleteResp.ok) {
throw new Error('Failed to delete routine item')
}
}
for (const item of items.value) {
@@ -385,17 +396,23 @@ async function handleSubmit(form: { name: string; points: number; image_id: stri
const payload = { name: item.name, image_id: resolvedImageId, order: item.order }
if (item.id?.startsWith('temp-') || !item.id) {
await fetch(`/api/routine/${routineId}/item/add`, {
const addItemResp = await fetch(`/api/routine/${routineId}/item/add`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
if (!addItemResp.ok) {
throw new Error('Failed to add routine item')
}
} else {
await fetch(`/api/routine/${routineId}/item/${item.id}/edit`, {
const editItemResp = await fetch(`/api/routine/${routineId}/item/${item.id}/edit`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
if (!editItemResp.ok) {
throw new Error('Failed to edit routine item')
}
}
}

View File

@@ -3,13 +3,15 @@ import { defineComponent, nextTick } from 'vue'
import { mount } from '@vue/test-utils'
import RoutineEditView from '../RoutineEditView.vue'
const mockPush = vi.fn()
vi.mock('vue-router', () => ({
useRoute: vi.fn(() => ({
params: {},
query: { name: 'Timmy' },
})),
useRouter: vi.fn(() => ({
push: vi.fn(),
push: mockPush,
back: vi.fn(),
})),
}))
@@ -35,7 +37,7 @@ const EntityEditFormStub = defineComponent({
emits: ['submit', 'cancel', 'add-image'],
template: `
<div class="entity-edit-form-stub">
<button type="submit" class="submit-btn" :disabled="submitDisabled">Submit</button>
<button type="button" class="submit-btn" :disabled="submitDisabled" @click="$emit('submit', { name: 'Morning Routine', points: 5, image_id: null })">Submit</button>
<slot name="before-actions" />
</div>
`,
@@ -49,6 +51,9 @@ const ImagePickerStub = defineComponent({
describe('RoutineEditView', () => {
beforeEach(() => {
vi.clearAllMocks()
globalThis.fetch = vi
.fn()
.mockResolvedValue({ ok: true, json: async () => ({}) }) as typeof fetch
})
it('disables Create when there are no routine tasks', async () => {
@@ -69,4 +74,49 @@ describe('RoutineEditView', () => {
expect((submitButton.element as HTMLButtonElement).disabled).toBe(true)
expect(wrapper.findComponent(EntityEditFormStub).props('submitDisabled')).toBe(true)
})
it('creates routine items using nested routine id from create response', async () => {
const fetchMock = vi.fn<typeof fetch>()
fetchMock
.mockResolvedValueOnce({
ok: true,
json: async () => ({ message: 'Routine added', routine: { id: 'routine-123' } }),
} as Response)
.mockResolvedValueOnce({ ok: true, json: async () => ({ id: 'item-1' }) } as Response)
globalThis.fetch = fetchMock
const wrapper = mount(RoutineEditView, {
props: {},
global: {
stubs: {
EntityEditForm: EntityEditFormStub,
ImagePicker: ImagePickerStub,
},
},
})
await wrapper.find('.add-task-trigger').trigger('click')
await nextTick()
const nameInput = wrapper.find('#item-name')
await nameInput.setValue('Make bed')
await wrapper.find('.item-form-actions .btn.btn-primary').trigger('click')
await nextTick()
await wrapper.find('.submit-btn').trigger('click')
await nextTick()
expect(fetchMock).toHaveBeenNthCalledWith(
1,
'/api/routine/add',
expect.objectContaining({ method: 'PUT' }),
)
expect(fetchMock).toHaveBeenNthCalledWith(
2,
'/api/routine/routine-123/item/add',
expect.objectContaining({ method: 'PUT' }),
)
expect(mockPush).toHaveBeenCalledWith({ name: 'RoutineView' })
})
})