@forsakringskassan/vue-lib-template 1.0.4 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/files/Jenkinsfile +2 -0
- package/files/README.md +6 -5
- package/files/_editorconfig +3 -0
- package/files/_gitignore +3 -0
- package/files/_htmlvalidateignore +5 -0
- package/files/_prettierignore +4 -1
- package/files/api-extractor.lib.json +1 -1
- package/files/api-extractor.selectors.json +1 -1
- package/files/babel.config.js +2 -0
- package/files/cypress/support/common.ts +7 -6
- package/files/cypress/support/component.ts +5 -25
- package/files/cypress/support/main.scss +2 -0
- package/files/cypress/tsconfig.json +3 -3
- package/files/cypress.config.ts +14 -75
- package/files/docs/build.mts +16 -0
- package/files/docs/index.md +2 -0
- package/files/docs/kattinformation/index.md +28 -0
- package/files/docs/package.json +5 -2
- package/files/docs/src/bundle-mocks.mjs +22 -0
- package/files/docs/src/global.d.ts +7 -0
- package/files/docs/src/main.js +17 -0
- package/files/docs/src/main.scss +22 -0
- package/files/docs/src/setup.ts +6 -1
- package/files/docs/tsconfig.json +3 -0
- package/files/eslint.config.mjs +2 -0
- package/files/etc/lib.api.md +1 -1
- package/files/etc/selectors.api.md +13 -0
- package/files/package.json +47 -33
- package/files/renovate.json +2 -3
- package/files/src/CatInfoEdit.vue +57 -0
- package/files/src/CatInfoLogic.spec.ts +118 -0
- package/files/src/CatInfoShow.vue +27 -0
- package/files/src/FcCatInfoComponent.cy.ts +74 -0
- package/files/src/FcCatInfoComponent.vue +81 -0
- package/files/src/FcCatInfoComponentLiveExample.vue +42 -0
- package/files/src/cat-api-mock.spec.ts +27 -0
- package/files/src/cat-api.mock.mts +26 -0
- package/files/src/cat-api.spec.ts +65 -0
- package/files/src/cat-api.ts +15 -0
- package/files/src/cat-data.ts +40 -0
- package/files/src/cat-info-logic.ts +52 -0
- package/files/src/cat-types.ts +11 -0
- package/files/src/examples/CatInfoExample.vue +20 -0
- package/files/src/index.ts +3 -1
- package/files/src/local.ts +1 -1
- package/files/src/mocks.mts +6 -0
- package/files/src/selectors/FcCatInfoComponent.selectors.ts +74 -0
- package/files/src/selectors/index.ts +1 -1
- package/files/src/style.scss +1 -0
- package/files/src/vite-dev/app.vue +1 -1
- package/files/tsconfig.cypress.json +1 -1
- package/files/tsconfig.json +1 -1
- package/files/tsconfig.lib.json +8 -2
- package/files/tsconfig.selectors.json +1 -1
- package/files/vite.config.mts +5 -1
- package/hooks/install.mjs +9 -1
- package/index.js +1 -0
- package/package.json +25 -16
- package/files/cypress/support/commands/forced-colors.ts +0 -33
- package/files/cypress/support/component-index.html +0 -14
- package/files/cypress/types/cy-mount.d.ts +0 -9
- package/files/cypress/types/forced-colors.d.ts +0 -18
- package/files/docs/components/AwesomeComponent.md +0 -27
- package/files/docs/components/index.md +0 -5
- package/files/src/AwesomeComponent.cy.ts +0 -9
- package/files/src/AwesomeComponent.vue +0 -45
- package/files/src/AwesomeComponentLogic.spec.ts +0 -30
- package/files/src/awesome-component-logic.ts +0 -28
- package/files/src/examples/DummyExample.vue +0 -7
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { FButton, FNumericTextField, FTextField, FValidationForm } from "@fkui/vue";
|
|
3
|
+
import { type Cat } from "./cat-types";
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
cat: Cat;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const props = defineProps<Props>();
|
|
10
|
+
|
|
11
|
+
const emit = defineEmits<{
|
|
12
|
+
(e: "submit", cat: Cat): void;
|
|
13
|
+
(e: "cancel"): void;
|
|
14
|
+
}>();
|
|
15
|
+
|
|
16
|
+
// Create a local copy for editing
|
|
17
|
+
const editForm = { ...props.cat };
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<template>
|
|
21
|
+
<div data-test="cat-info-edit">
|
|
22
|
+
<f-validation-form @submit="emit('submit', editForm)">
|
|
23
|
+
<template #error-message> Oj, du har glömt fylla i något. Gå till: </template>
|
|
24
|
+
<template #default>
|
|
25
|
+
<f-text-field v-model="editForm.name" v-validation.required.maxLength="100" v-test="'edit-name'">
|
|
26
|
+
Namn
|
|
27
|
+
</f-text-field>
|
|
28
|
+
|
|
29
|
+
<f-numeric-text-field v-model="editForm.age" v-validation.required v-test="'edit-age'">
|
|
30
|
+
Ålder
|
|
31
|
+
</f-numeric-text-field>
|
|
32
|
+
|
|
33
|
+
<f-text-field v-model="editForm.breed" v-validation.required.maxLength="100" v-test="'edit-breed'">
|
|
34
|
+
Ras
|
|
35
|
+
</f-text-field>
|
|
36
|
+
|
|
37
|
+
<f-text-field v-model="editForm.color" v-validation.required.maxLength="100" v-test="'edit-color'">
|
|
38
|
+
Färg
|
|
39
|
+
</f-text-field>
|
|
40
|
+
|
|
41
|
+
<f-text-field
|
|
42
|
+
v-model="editForm.favoriteFood"
|
|
43
|
+
v-validation.maxLength="500"
|
|
44
|
+
v-test="'edit-favorite-food'"
|
|
45
|
+
>
|
|
46
|
+
Favoritmat
|
|
47
|
+
</f-text-field>
|
|
48
|
+
<div class="button-group">
|
|
49
|
+
<f-button class="button-group__item" size="large" variant="primary" type="submit"> Spara </f-button>
|
|
50
|
+
<f-button class="button-group__item" size="large" variant="secondary" @click="emit('cancel')">
|
|
51
|
+
Avbryt
|
|
52
|
+
</f-button>
|
|
53
|
+
</div>
|
|
54
|
+
</template>
|
|
55
|
+
</f-validation-form>
|
|
56
|
+
</div>
|
|
57
|
+
</template>
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { catGetById } from "./cat-api";
|
|
3
|
+
import { useCatInfo } from "./cat-info-logic";
|
|
4
|
+
|
|
5
|
+
vi.mock(import("./cat-api"));
|
|
6
|
+
//vi.useFakeTimers();
|
|
7
|
+
|
|
8
|
+
describe("useCatInfo", () => {
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
vi.mocked(catGetById).mockReset();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("should have initial state", () => {
|
|
14
|
+
expect.assertions(3);
|
|
15
|
+
const { loading, cat, error } = useCatInfo();
|
|
16
|
+
expect(loading.value).toBe(false);
|
|
17
|
+
expect(cat.value).toBeNull();
|
|
18
|
+
expect(error.value).toBeNull();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("should set loading then data for valid cat ID", async () => {
|
|
22
|
+
expect.assertions(5);
|
|
23
|
+
const { loading, cat, error, fetchCat } = useCatInfo();
|
|
24
|
+
const mockCat = {
|
|
25
|
+
id: "whiskers-001",
|
|
26
|
+
name: "Whiskers McFluffington",
|
|
27
|
+
age: 3,
|
|
28
|
+
breed: "European Shorthair",
|
|
29
|
+
color: "Orange Tabby",
|
|
30
|
+
favoriteFood: "Tuna",
|
|
31
|
+
};
|
|
32
|
+
vi.mocked(catGetById).mockResolvedValue(mockCat);
|
|
33
|
+
|
|
34
|
+
const fetchPromise = fetchCat("whiskers-001");
|
|
35
|
+
|
|
36
|
+
// loading should be true immediately
|
|
37
|
+
expect(loading.value).toBe(true);
|
|
38
|
+
|
|
39
|
+
await fetchPromise;
|
|
40
|
+
|
|
41
|
+
expect(loading.value).toBe(false);
|
|
42
|
+
expect(cat.value).not.toBeNull();
|
|
43
|
+
expect(cat.value?.name).toBe("Whiskers McFluffington");
|
|
44
|
+
expect(error.value).toBeNull();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("should set error for invalid cat ID", async () => {
|
|
48
|
+
expect.assertions(4);
|
|
49
|
+
const { loading, cat, error, fetchCat } = useCatInfo();
|
|
50
|
+
vi.mocked(catGetById).mockRejectedValue(
|
|
51
|
+
new Error("Cat with ID 'invalid-id' not found"),
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
await fetchCat("invalid-id");
|
|
55
|
+
|
|
56
|
+
expect(loading.value).toBe(false);
|
|
57
|
+
expect(cat.value).toBeNull();
|
|
58
|
+
expect(error.value).not.toBeNull();
|
|
59
|
+
expect(error.value).toContain("not found");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("should modify cat data", () => {
|
|
63
|
+
expect.assertions(2);
|
|
64
|
+
const { cat, updateCat } = useCatInfo();
|
|
65
|
+
const testCat = {
|
|
66
|
+
id: "test",
|
|
67
|
+
name: "Original",
|
|
68
|
+
age: 5,
|
|
69
|
+
breed: "Test",
|
|
70
|
+
color: "Test",
|
|
71
|
+
favoriteFood: "Test",
|
|
72
|
+
};
|
|
73
|
+
cat.value = testCat;
|
|
74
|
+
|
|
75
|
+
updateCat({ ...testCat, name: "Updated" });
|
|
76
|
+
|
|
77
|
+
expect(cat.value.name).toBe("Updated");
|
|
78
|
+
expect(cat.value.age).toBe(5);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("should fetch cat for luna-002", async () => {
|
|
82
|
+
expect.assertions(2);
|
|
83
|
+
const { cat, fetchCat } = useCatInfo();
|
|
84
|
+
const mockCat = {
|
|
85
|
+
id: "luna-002",
|
|
86
|
+
name: "Luna the Midnight Prowler",
|
|
87
|
+
age: 2,
|
|
88
|
+
breed: "Bombay",
|
|
89
|
+
color: "Midnight Black",
|
|
90
|
+
favoriteFood: "Salmon",
|
|
91
|
+
};
|
|
92
|
+
vi.mocked(catGetById).mockResolvedValue(mockCat);
|
|
93
|
+
|
|
94
|
+
await fetchCat("luna-002");
|
|
95
|
+
|
|
96
|
+
expect(cat.value!.name).toBe("Luna the Midnight Prowler");
|
|
97
|
+
expect(cat.value!.color).toBe("Midnight Black");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("should fetch cat for muffin-003", async () => {
|
|
101
|
+
expect.assertions(2);
|
|
102
|
+
const { cat, fetchCat } = useCatInfo();
|
|
103
|
+
const mockCat = {
|
|
104
|
+
id: "muffin-003",
|
|
105
|
+
name: "Sir Muffin III",
|
|
106
|
+
age: 5,
|
|
107
|
+
breed: "Maine Coon",
|
|
108
|
+
color: "Calico",
|
|
109
|
+
favoriteFood: "Chicken",
|
|
110
|
+
};
|
|
111
|
+
vi.mocked(catGetById).mockResolvedValue(mockCat);
|
|
112
|
+
|
|
113
|
+
await fetchCat("muffin-003");
|
|
114
|
+
|
|
115
|
+
expect(cat.value!.name).toBe("Sir Muffin III");
|
|
116
|
+
expect(cat.value!.breed).toBe("Maine Coon");
|
|
117
|
+
});
|
|
118
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import { FDefinitionList } from "@fkui/vue";
|
|
4
|
+
import { type Cat } from "./cat-types";
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
cat: Cat;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const props = defineProps<Props>();
|
|
11
|
+
|
|
12
|
+
const catDefinitions = computed(() => {
|
|
13
|
+
return [
|
|
14
|
+
{ term: "Namn", definition: props.cat.name },
|
|
15
|
+
{ term: "Ålder", definition: `${props.cat.age} år` },
|
|
16
|
+
{ term: "Ras", definition: props.cat.breed },
|
|
17
|
+
{ term: "Färg", definition: props.cat.color },
|
|
18
|
+
{ term: "Favoritmat", definition: props.cat.favoriteFood },
|
|
19
|
+
];
|
|
20
|
+
});
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<div data-test="cat-info-view">
|
|
25
|
+
<f-definition-list :definitions="catDefinitions" />
|
|
26
|
+
</div>
|
|
27
|
+
</template>
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { FLoaderSelectors } from "@fkui/vue/selectors";
|
|
2
|
+
import FcCatInfoComponent from "./FcCatInfoComponent.vue";
|
|
3
|
+
import { cats } from "./cat-data";
|
|
4
|
+
import { FcCatInfoComponentSelectors } from "./selectors";
|
|
5
|
+
|
|
6
|
+
describe("FcCatInfoComponent", () => {
|
|
7
|
+
const testCat = cats[0];
|
|
8
|
+
const catInfoSelectors = FcCatInfoComponentSelectors();
|
|
9
|
+
const loaderSelectors = FLoaderSelectors();
|
|
10
|
+
|
|
11
|
+
it("verifies loading text", () => {
|
|
12
|
+
cy.mount(FcCatInfoComponent, {
|
|
13
|
+
props: {
|
|
14
|
+
catId: testCat.id,
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
cy.get(catInfoSelectors.loadingContainer()).should("be.visible");
|
|
19
|
+
cy.get(loaderSelectors.waitText()).should(
|
|
20
|
+
"contain.text",
|
|
21
|
+
"Hämtar kattinformation...",
|
|
22
|
+
);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("verifies all fields in view mode have correct data", () => {
|
|
26
|
+
cy.mount(FcCatInfoComponent, {
|
|
27
|
+
props: {
|
|
28
|
+
catId: testCat.id,
|
|
29
|
+
isEditable: false,
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Wait for loading to disappear
|
|
34
|
+
cy.get(catInfoSelectors.loadingContainer()).should("not.exist");
|
|
35
|
+
|
|
36
|
+
const view = catInfoSelectors.viewContainer();
|
|
37
|
+
cy.get(view).should("be.visible");
|
|
38
|
+
|
|
39
|
+
cy.get(view).contains(testCat.name).should("be.visible");
|
|
40
|
+
cy.get(view).contains(`${testCat.age} år`).should("be.visible");
|
|
41
|
+
cy.get(view).contains(testCat.breed).should("be.visible");
|
|
42
|
+
cy.get(view).contains(testCat.color).should("be.visible");
|
|
43
|
+
cy.get(view).contains(testCat.favoriteFood).should("be.visible");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("verifies all fields in edit mode have correct data", () => {
|
|
47
|
+
cy.mount(FcCatInfoComponent, {
|
|
48
|
+
props: {
|
|
49
|
+
catId: testCat.id,
|
|
50
|
+
isEditable: true,
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Wait for loading to disappear
|
|
55
|
+
cy.get(catInfoSelectors.loadingContainer()).should("not.exist");
|
|
56
|
+
cy.get(catInfoSelectors.editContainer()).should("be.visible");
|
|
57
|
+
|
|
58
|
+
cy.get(catInfoSelectors.nameInput())
|
|
59
|
+
.should("be.visible")
|
|
60
|
+
.should("have.value", testCat.name);
|
|
61
|
+
cy.get(catInfoSelectors.ageInput())
|
|
62
|
+
.should("be.visible")
|
|
63
|
+
.should("have.value", testCat.age.toString());
|
|
64
|
+
cy.get(catInfoSelectors.breedInput())
|
|
65
|
+
.should("be.visible")
|
|
66
|
+
.should("have.value", testCat.breed);
|
|
67
|
+
cy.get(catInfoSelectors.colorInput())
|
|
68
|
+
.should("be.visible")
|
|
69
|
+
.should("have.value", testCat.color);
|
|
70
|
+
cy.get(catInfoSelectors.favoriteFoodInput())
|
|
71
|
+
.should("be.visible")
|
|
72
|
+
.should("have.value", testCat.favoriteFood);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, watch } from "vue";
|
|
3
|
+
import { FCard, FLoader } from "@fkui/vue";
|
|
4
|
+
import CatInfoEdit from "./CatInfoEdit.vue";
|
|
5
|
+
import CatInfoShow from "./CatInfoShow.vue";
|
|
6
|
+
import { useCatInfo } from "./cat-info-logic";
|
|
7
|
+
import { type Cat } from "./cat-types";
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
/**
|
|
11
|
+
* The cat ID to display
|
|
12
|
+
*/
|
|
13
|
+
catId: string;
|
|
14
|
+
/**
|
|
15
|
+
* Whether the cat information should be editable
|
|
16
|
+
*/
|
|
17
|
+
isEditable?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const props = defineProps<Props>();
|
|
21
|
+
|
|
22
|
+
const { loading, cat, fetchCat, updateCat } = useCatInfo();
|
|
23
|
+
|
|
24
|
+
const editForm = ref({ id: "", name: "", age: 0, breed: "", color: "", favoriteFood: "" });
|
|
25
|
+
|
|
26
|
+
watch(
|
|
27
|
+
() => props.catId,
|
|
28
|
+
async (newCatId) => {
|
|
29
|
+
if (!newCatId) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
await fetchCat(newCatId);
|
|
34
|
+
if (cat.value) {
|
|
35
|
+
editForm.value = { ...cat.value };
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
{ immediate: true },
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Submit the edited cat information
|
|
43
|
+
*/
|
|
44
|
+
function onSubmit(updatedCat: Cat): void {
|
|
45
|
+
updateCat(updatedCat);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Cancel editing and reload original data
|
|
50
|
+
*/
|
|
51
|
+
async function onCancel(): Promise<void> {
|
|
52
|
+
if (!props.catId) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
await fetchCat(props.catId);
|
|
57
|
+
if (cat.value) {
|
|
58
|
+
editForm.value = { ...cat.value };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
<template>
|
|
64
|
+
<f-card>
|
|
65
|
+
<template #header>
|
|
66
|
+
<h2>Kattinformation</h2>
|
|
67
|
+
</template>
|
|
68
|
+
<template #default>
|
|
69
|
+
<div>
|
|
70
|
+
<!-- Loading state -->
|
|
71
|
+
<f-loader v-if="loading" show data-test="cat-info-loading"> Hämtar kattinformation... </f-loader>
|
|
72
|
+
|
|
73
|
+
<!-- Cat data - View mode -->
|
|
74
|
+
<cat-info-show v-else-if="cat && !isEditable" :cat />
|
|
75
|
+
|
|
76
|
+
<!-- Cat data - Edit mode -->
|
|
77
|
+
<cat-info-edit v-else-if="cat && isEditable" :cat @submit="onSubmit" @cancel="onCancel" />
|
|
78
|
+
</div>
|
|
79
|
+
</template>
|
|
80
|
+
</f-card>
|
|
81
|
+
</template>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { defineComponent } from "vue";
|
|
3
|
+
import { FCheckboxField, FSelectField } from "@fkui/vue";
|
|
4
|
+
import { LiveExample } from "@forsakringskassan/docs-live-example";
|
|
5
|
+
import FcCatInfoComponent from "./FcCatInfoComponent.vue";
|
|
6
|
+
import { getCatIds } from "./cat-data";
|
|
7
|
+
|
|
8
|
+
export default defineComponent({
|
|
9
|
+
name: "FcCatInfoComponentLiveExample",
|
|
10
|
+
components: { LiveExample, FSelectField, FCheckboxField },
|
|
11
|
+
data() {
|
|
12
|
+
return {
|
|
13
|
+
selectedCatId: "whiskers-001",
|
|
14
|
+
isEditable: false,
|
|
15
|
+
catIds: getCatIds(),
|
|
16
|
+
};
|
|
17
|
+
},
|
|
18
|
+
computed: {
|
|
19
|
+
components(): object {
|
|
20
|
+
return { FcCatInfoComponent };
|
|
21
|
+
},
|
|
22
|
+
editableAttr(): string {
|
|
23
|
+
return this.isEditable ? " is-editable" : "";
|
|
24
|
+
},
|
|
25
|
+
template(): string {
|
|
26
|
+
return /* HTML */ ` <fc-cat-info-component cat-id="${this.selectedCatId}" ${this.editableAttr} /> `;
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<live-example :components :template>
|
|
34
|
+
<f-select-field v-model="selectedCatId">
|
|
35
|
+
<template #label>Välj katt</template>
|
|
36
|
+
<option value="whiskers-001">Whiskers McFluffington</option>
|
|
37
|
+
<option value="luna-002">Luna the Midnight Prowler</option>
|
|
38
|
+
<option value="muffin-003">Sir Muffin III</option>
|
|
39
|
+
</f-select-field>
|
|
40
|
+
<f-checkbox-field v-model="isEditable" :value="true"> Redigerbar </f-checkbox-field>
|
|
41
|
+
</live-example>
|
|
42
|
+
</template>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { matchRequest } from "@forsakringskassan/apimock-express/browser";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import mocks from "./mocks.mjs";
|
|
4
|
+
|
|
5
|
+
describe("Cat API Mock Reproduction", () => {
|
|
6
|
+
it("should return 200 for a valid cat id /api/cat?id=whiskers-001", async () => {
|
|
7
|
+
expect.assertions(2);
|
|
8
|
+
const req = new Request(
|
|
9
|
+
"http://localhost:8080/api/cat?id=whiskers-001",
|
|
10
|
+
);
|
|
11
|
+
const response = await matchRequest(mocks, req);
|
|
12
|
+
expect(response.status).toBe(200);
|
|
13
|
+
|
|
14
|
+
const data = await response.json();
|
|
15
|
+
expect(data.id).toBe("whiskers-001");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("should return 404 for an invalid cat id /api/cat?id=unknown", async () => {
|
|
19
|
+
expect.assertions(2);
|
|
20
|
+
const req = new Request("http://localhost:8080/api/cat?id=unknown");
|
|
21
|
+
const response = await matchRequest(mocks, req);
|
|
22
|
+
expect(response.status).toBe(404);
|
|
23
|
+
|
|
24
|
+
const data = await response.json();
|
|
25
|
+
expect(data.error).toContain("not found");
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { defineMock } from "@forsakringskassan/apimock-express/helpers";
|
|
2
|
+
import { cats } from "./cat-data.ts";
|
|
3
|
+
import type { Cat } from "./cat-types.ts";
|
|
4
|
+
|
|
5
|
+
export default defineMock<Cat | { error: string }>({
|
|
6
|
+
meta: {
|
|
7
|
+
url: "/cat",
|
|
8
|
+
method: "GET",
|
|
9
|
+
},
|
|
10
|
+
defaultResponse(params) {
|
|
11
|
+
const id = params.parameters?.id;
|
|
12
|
+
const cat = cats.find((c) => c.id === id);
|
|
13
|
+
if (cat) {
|
|
14
|
+
return {
|
|
15
|
+
status: 200,
|
|
16
|
+
body: cat,
|
|
17
|
+
delay: 300,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
status: 404,
|
|
22
|
+
body: { error: `Cat with ID "${id}" not found` },
|
|
23
|
+
delay: 300,
|
|
24
|
+
};
|
|
25
|
+
},
|
|
26
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { catGetById } from "./cat-api";
|
|
3
|
+
|
|
4
|
+
describe("catGetById", () => {
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
vi.stubGlobal("fetch", vi.fn());
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it("should return a cat when the response is ok", async () => {
|
|
10
|
+
expect.assertions(2);
|
|
11
|
+
const mockCat = {
|
|
12
|
+
id: "whiskers-001",
|
|
13
|
+
name: "Whiskers McFluffington",
|
|
14
|
+
age: 3,
|
|
15
|
+
breed: "European Shorthair",
|
|
16
|
+
color: "Orange Tabby",
|
|
17
|
+
favoriteFood: "Tuna",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
vi.mocked(fetch).mockResolvedValue({
|
|
21
|
+
ok: true,
|
|
22
|
+
json: () => Promise.resolve(mockCat),
|
|
23
|
+
} as Response);
|
|
24
|
+
|
|
25
|
+
const result = await catGetById("whiskers-001");
|
|
26
|
+
|
|
27
|
+
expect(fetch).toHaveBeenCalledWith("/api/cat?id=whiskers-001");
|
|
28
|
+
expect(result).toEqual(mockCat);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should throw an error with API message when response is not ok", async () => {
|
|
32
|
+
expect.assertions(1);
|
|
33
|
+
const errorResponse = { error: "Cat with ID 'unknown' not found" };
|
|
34
|
+
|
|
35
|
+
vi.mocked(fetch).mockResolvedValue({
|
|
36
|
+
ok: false,
|
|
37
|
+
json: () => Promise.resolve(errorResponse),
|
|
38
|
+
} as Response);
|
|
39
|
+
|
|
40
|
+
await expect(catGetById("unknown")).rejects.toThrow(
|
|
41
|
+
"Cat with ID 'unknown' not found",
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("should throw a generic error when response is not ok and no error message is provided", async () => {
|
|
46
|
+
expect.assertions(1);
|
|
47
|
+
vi.mocked(fetch).mockResolvedValue({
|
|
48
|
+
ok: false,
|
|
49
|
+
json: () => Promise.resolve({}),
|
|
50
|
+
} as Response);
|
|
51
|
+
|
|
52
|
+
await expect(catGetById("unknown")).rejects.toThrow(
|
|
53
|
+
"Failed to fetch cat with ID unknown",
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("should throw an error when fetch fails", async () => {
|
|
58
|
+
expect.assertions(1);
|
|
59
|
+
vi.mocked(fetch).mockRejectedValue(new Error("Network failure"));
|
|
60
|
+
|
|
61
|
+
await expect(catGetById("whiskers-001")).rejects.toThrow(
|
|
62
|
+
"Network failure",
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Cat } from "./cat-types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @internal
|
|
5
|
+
*/
|
|
6
|
+
export async function catGetById(id: string): Promise<Cat> {
|
|
7
|
+
const response = await fetch(`/api/cat?id=${id}`);
|
|
8
|
+
|
|
9
|
+
if (!response.ok) {
|
|
10
|
+
const errorData = (await response.json()) as { error?: string };
|
|
11
|
+
throw new Error(errorData.error ?? `Failed to fetch cat with ID ${id}`);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return (await response.json()) as Cat;
|
|
15
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Cat } from "./cat-types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mock cat data for the FcCatInfoComponent
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const cats: Cat[] = [
|
|
8
|
+
{
|
|
9
|
+
id: "whiskers-001",
|
|
10
|
+
name: "Whiskers McFluffington",
|
|
11
|
+
age: 3,
|
|
12
|
+
breed: "Norsk skogkatt",
|
|
13
|
+
color: "Orange Tabby",
|
|
14
|
+
favoriteFood: "Sardiner i champagne-sås",
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
id: "luna-002",
|
|
18
|
+
name: "Luna the Midnight Prowler",
|
|
19
|
+
age: 5,
|
|
20
|
+
breed: "Svart huskatt",
|
|
21
|
+
color: "Midnight Black",
|
|
22
|
+
favoriteFood: "Döda flugor",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: "muffin-003",
|
|
26
|
+
name: "Sir Muffin III",
|
|
27
|
+
age: 2,
|
|
28
|
+
breed: "Maine Coon",
|
|
29
|
+
color: "Silver and Gray",
|
|
30
|
+
favoriteFood: "Lyxig tonfisk-mousse med en touch av kattmynta",
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get all available cat IDs
|
|
36
|
+
* @returns Array of cat IDs
|
|
37
|
+
*/
|
|
38
|
+
export function getCatIds(): string[] {
|
|
39
|
+
return cats.map((cat) => cat.id);
|
|
40
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Ref } from "vue";
|
|
2
|
+
import { ref } from "vue";
|
|
3
|
+
import { catGetById } from "./cat-api";
|
|
4
|
+
import type { Cat } from "./cat-types";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Business logic for the FcCatInfoComponent.
|
|
8
|
+
* Manages fetching and updating cat information from mock backend.
|
|
9
|
+
*/
|
|
10
|
+
export function useCatInfo(): {
|
|
11
|
+
loading: Ref<boolean>;
|
|
12
|
+
cat: Ref<Cat | null>;
|
|
13
|
+
error: Ref<string | null>;
|
|
14
|
+
fetchCat: (catId: string) => Promise<void>;
|
|
15
|
+
updateCat: (newCat: Cat) => void;
|
|
16
|
+
} {
|
|
17
|
+
const loading = ref(false);
|
|
18
|
+
const cat = ref<Cat | null>(null);
|
|
19
|
+
const error = ref<string | null>(null);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Fetch cat data from mock backend
|
|
23
|
+
*/
|
|
24
|
+
async function fetchCat(catId: string): Promise<void> {
|
|
25
|
+
loading.value = true;
|
|
26
|
+
error.value = null;
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
// Get cat from mock backend
|
|
30
|
+
const fetchedCat = await catGetById(catId);
|
|
31
|
+
cat.value = { ...fetchedCat };
|
|
32
|
+
} catch (err) {
|
|
33
|
+
error.value =
|
|
34
|
+
err instanceof Error ? err.message : "Unknown error occurred";
|
|
35
|
+
cat.value = null;
|
|
36
|
+
} finally {
|
|
37
|
+
loading.value = false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Update cat information
|
|
43
|
+
* @param updates - Partial cat object with fields to update
|
|
44
|
+
*/
|
|
45
|
+
function updateCat(newCat: Cat): void {
|
|
46
|
+
if (cat.value) {
|
|
47
|
+
cat.value = { ...newCat };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return { loading, cat, error, fetchCat, updateCat };
|
|
52
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref } from "vue";
|
|
3
|
+
import FcCatInfoComponent from "../FcCatInfoComponent.vue";
|
|
4
|
+
|
|
5
|
+
const selectedCatId = ref("whiskers-001");
|
|
6
|
+
const isEditable = ref(false);
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<template>
|
|
10
|
+
<div class="cat-example__component">
|
|
11
|
+
<fc-cat-info-component :cat-id="selectedCatId" :is-editable />
|
|
12
|
+
</div>
|
|
13
|
+
</template>
|
|
14
|
+
|
|
15
|
+
<style lang="scss" scoped>
|
|
16
|
+
.cat-example__component {
|
|
17
|
+
margin: 2rem;
|
|
18
|
+
max-width: 800px;
|
|
19
|
+
}
|
|
20
|
+
</style>
|
package/files/src/index.ts
CHANGED
package/files/src/local.ts
CHANGED
|
@@ -6,9 +6,9 @@ import {
|
|
|
6
6
|
ValidationPlugin,
|
|
7
7
|
setRunningContext,
|
|
8
8
|
} from "@fkui/vue";
|
|
9
|
-
import "./local.scss";
|
|
10
9
|
import { type SetupOptions } from "@forsakringskassan/vite-lib-config";
|
|
11
10
|
import "@fkui/icon-lib-default/dist/f";
|
|
11
|
+
import "./local.scss";
|
|
12
12
|
|
|
13
13
|
export function setup(options: SetupOptions): void {
|
|
14
14
|
const { rootComponent, selector } = options;
|