Skip to content

Commit

Permalink
maia v1.0 の変更を移植 (#2269)
Browse files Browse the repository at this point in the history
  • Loading branch information
KentaHizume authored Jan 15, 2025
1 parent 365e584 commit c35dee2
Show file tree
Hide file tree
Showing 33 changed files with 1,552 additions and 721 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { HttpResponse, http } from 'msw';
import type { GetLoginUserResponse } from '@/generated/api-client';
import { HttpStatusCode } from 'axios';
import { Roles } from '@/shared/constants/roles';

const user: GetLoginUserResponse = {
userName: '[email protected]',
roles: ['Admin'],
roles: [Roles.ADMIN],
};

export const usersHandlers = [
Expand Down
53 changes: 27 additions & 26 deletions samples/Dressca/dressca-frontend/admin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,53 +22,54 @@
"openapi-client:generate": "openapi-generator-cli generate -g typescript-axios -i ./../../dressca-backend/src/Dressca.Web.Admin/dressca-admin-api.json --additional-properties=withSeparateModelsAndApi=true,modelPackage=models,apiPackage=api,supportsES6=true -o ./src/generated/api-client"
},
"dependencies": {
"@heroicons/vue": "^2.1.5",
"axios": "^1.7.7",
"msw": "^2.7.0",
"@vee-validate/yup": "^4.13.2",
"pinia": "^2.2.5",
"vee-validate": "^4.14.4",
"vitest": "^2.1.4",
"vue": "^3.5.12",
"vue-router": "^4.4.5",
"yup": "^1.4.0"
"@heroicons/vue": "^2.2.0",
"@vee-validate/yup": "^4.15.0",
"axios": "^1.7.9",
"msw": "2.7.0",
"pinia": "^2.3.0",
"vee-validate": "^4.15.0",
"vitest": "^2.1.8",
"vue": "^3.5.13",
"vue-router": "^4.5.0",
"yup": "^1.6.1"
},
"msw": {
"workerDirectory": [
"public"
]
},
"devDependencies": {
"@openapitools/openapi-generator-cli": "^2.15.0",
"@rushstack/eslint-patch": "^1.10.4",
"@openapitools/openapi-generator-cli": "^2.15.3",
"@pinia/testing": "^0.1.7",
"@rushstack/eslint-patch": "^1.10.5",
"@tsconfig/node20": "^20.1.4",
"@types/jsdom": "^21.1.7",
"@types/node": "^22.8.5",
"@vitejs/plugin-vue": "^5.1.4",
"@vitejs/plugin-vue-jsx": "^4.0.0",
"@types/node": "^22.10.5",
"@vitejs/plugin-vue": "^5.2.1",
"@vitejs/plugin-vue-jsx": "^4.1.1",
"@vue/eslint-config-airbnb-with-typescript": "^8.0.0",
"@vue/eslint-config-prettier": "^9.0.0",
"@vue/eslint-config-typescript": "^13.0.0",
"@vue/test-utils": "^2.4.6",
"@vue/tsconfig": "^0.5.1",
"@vue/tsconfig": "^0.7.0",
"autoprefixer": "^10.4.20",
"cypress": "^13.15.1",
"cypress": "^13.17.0",
"eslint": "^8.57.0",
"eslint-plugin-cypress": "^3.4.0",
"eslint-plugin-vue": "^9.30.0",
"jsdom": "^25.0.1",
"npm-run-all2": "^7.0.1",
"postcss": "^8.4.47",
"eslint-plugin-vue": "^9.32.0",
"jsdom": "^26.0.0",
"npm-run-all2": "^7.0.2",
"postcss": "^8.4.49",
"postcss-nesting": "^13.0.1",
"prettier": "^3.3.3",
"start-server-and-test": "^2.0.8",
"stylelint": "^16.9.0",
"prettier": "^3.4.2",
"start-server-and-test": "^2.0.9",
"stylelint": "^16.12.0",
"stylelint-config-recommended-vue": "^1.5.0",
"stylelint-config-standard": "^36.0.1",
"stylelint-prettier": "^5.0.2",
"tailwindcss": "^3.4.14",
"tailwindcss": "^3.4.17",
"typescript": "5.3.3",
"vite": "^5.4.8",
"vue-tsc": "^2.1.10"
"vue-tsc": "^2.2.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* ユーザーのロールを表す文字列列挙型です。
*/
export enum Roles {
ADMIN = 'ROLE_ADMIN',
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,16 @@ export const useAuthenticationStore = defineStore({
isAuthenticated(state) {
return state.authenticationState;
},
/**
* ユーザーが特定のロールに属するかどうかを判定する関数を取得します。
* ストアのゲッターには直接パラメーターを渡すことができないので、
* 関数を経由してパラメーターを受け取る必要があります。
* サンプルアプリでは、必ず Admin ロールを持つユーザーとしてログインするようになっています。
* @param state 状態。
* @returns ユーザーが特定のロールに属するかどうかを判定する関数。
*/
isInRole(state) {
return (role: string) => state.userRoles.includes(role);
},
},
});
Original file line number Diff line number Diff line change
@@ -1,24 +1,37 @@
import { describe, it, expect, vi, beforeAll } from 'vitest';
import { flushPromises, mount, VueWrapper } from '@vue/test-utils';
import { router } from '@/router';
import { createPinia, setActivePinia } from 'pinia';
import { createTestingPinia, type TestingPinia } from '@pinia/testing';
import { createCustomErrorHandler } from '@/shared/error-handler/custom-error-handler';
import ItemsAddView from '@/views/catalog/ItemsAddView.vue';
import { Roles } from '@/shared/constants/roles';

async function getWrapper() {
const pinia = createPinia();
setActivePinia(pinia);
function CreateLoginState(userRoles: string[]) {
return createTestingPinia({
initialState: {
authentication: {
userRoles,
},
},
createSpy: vi.fn, // 明示的に設定する必要があります。
stubActions: false, // 結合テストなので、アクションはモック化しないように設定します。
});
}

async function getWrapper(pinia: TestingPinia) {
const customErrorHandler = createCustomErrorHandler();
return mount(ItemsAddView, {
global: { plugins: [pinia, router, customErrorHandler] },
});
}

describe('アイテムを追加できる', () => {
describe('管理者ロール_アイテムを追加できる', () => {
let loginState: TestingPinia;
let wrapper: VueWrapper;

beforeAll(async () => {
wrapper = await getWrapper();
loginState = CreateLoginState([Roles.ADMIN]);
wrapper = await getWrapper(loginState);
});

it('追加画面に遷移できる', async () => {
Expand All @@ -41,3 +54,29 @@ describe('アイテムを追加できる', () => {
expect(wrapper.html()).toContain('カタログアイテムを追加しました。');
});
});

describe('ゲストロール_アイテム追加ボタンが非活性', () => {
let loginState: TestingPinia;
let wrapper: VueWrapper;

beforeAll(async () => {
loginState = CreateLoginState(['ROLE_GUEST']);
wrapper = await getWrapper(loginState);
});

it('追加画面に遷移できる', async () => {
// Arrange
// Act
await flushPromises();
// Assert
expect(wrapper.html()).toContain('カタログアイテム追加');
});

it('追加ボタンが非活性', async () => {
// Arrange
// Act
const button = wrapper.find('button');
// Assert
expect(button.attributes('disabled')).toBeDefined();
});
});
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
import { describe, it, expect, vi, beforeAll } from 'vitest';
import { flushPromises, mount, VueWrapper } from '@vue/test-utils';
import { createPinia, setActivePinia } from 'pinia';
import { createTestingPinia, type TestingPinia } from '@pinia/testing';
import { createCustomErrorHandler } from '@/shared/error-handler/custom-error-handler';
import ItemsEditView from '@/views/catalog/ItemsEditView.vue';
import { router } from '@/router';
import { Roles } from '@/shared/constants/roles';

async function getWrapper() {
const pinia = createPinia();
setActivePinia(pinia);
function CreateLoginState(userRoles: string[]) {
return createTestingPinia({
initialState: {
authentication: {
userRoles,
},
},
createSpy: vi.fn, // 明示的に設定する必要があります。
stubActions: false, // 結合テストなので、アクションはモック化しないように設定します。
});
}

async function getWrapper(pinia: TestingPinia) {
const customErrorHandler = createCustomErrorHandler();
router.push({ name: 'catalog/items/edit', params: { itemId: 1 } });
await router.isReady();
Expand All @@ -16,11 +27,13 @@ async function getWrapper() {
});
}

describe('アイテムが削除できる', () => {
describe('管理者ロール_アイテムが削除できる', () => {
let loginState: TestingPinia;
let wrapper: VueWrapper;

beforeAll(async () => {
wrapper = await getWrapper();
loginState = CreateLoginState([Roles.ADMIN]);
wrapper = await getWrapper(loginState);
});

it('編集画面に遷移できる', async () => {
Expand Down Expand Up @@ -76,11 +89,39 @@ describe('アイテムが削除できる', () => {
});
});

describe('アイテムが更新できる', () => {
describe('ゲストロール_アイテム削除ボタンが非活性', () => {
let loginState: TestingPinia;
let wrapper: VueWrapper;

beforeAll(async () => {
loginState = CreateLoginState(['ROLE_GUEST']);
wrapper = await getWrapper(loginState);
});

it('編集画面に遷移できる', async () => {
// Arrange
// Act
await flushPromises();
// Assert
expect(wrapper.html()).toContain('カタログアイテム編集');
});

it('削除ボタンが非活性', async () => {
// Arrange
// Act
const deleteButton = wrapper.findAll('button')[0];
// Assert
expect(deleteButton.attributes('disabled')).toBeDefined();
});
});

describe('管理者ロール_アイテムが更新できる', () => {
let loginState: TestingPinia;
let wrapper: VueWrapper;

beforeAll(async () => {
wrapper = await getWrapper();
loginState = CreateLoginState([Roles.ADMIN]);
wrapper = await getWrapper(loginState);
});

it('編集画面に遷移できる', async () => {
Expand Down Expand Up @@ -130,3 +171,29 @@ describe('アイテムが更新できる', () => {
).toBeFalsy();
});
});

describe('ゲストロール_アイテム更新ボタンが非活性', () => {
let loginState: TestingPinia;
let wrapper: VueWrapper;

beforeAll(async () => {
loginState = CreateLoginState(['ROLE_GUEST']);
wrapper = await getWrapper(loginState);
});

it('編集画面に遷移できる', async () => {
// Arrange
// Act
await flushPromises();
// Assert
expect(wrapper.html()).toContain('カタログアイテム編集');
});

it('更新ボタンが非活性', async () => {
// Arrange
// Act
const editButton = wrapper.findAll('button')[1];
// Assert
expect(editButton.attributes('disabled')).toBeDefined();
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { storeToRefs } from 'pinia';
import {
fetchCategoriesAndBrands,
postCatalogItem,
Expand All @@ -14,9 +15,13 @@ import type {
GetCatalogBrandsResponse,
GetCatalogCategoriesResponse,
} from '@/generated/api-client';
import { useAuthenticationStore } from '@/stores/authentication/authentication';
import { Roles } from '@/shared/constants/roles';
const router = useRouter();
const customErrorHandler = useCustomErrorHandler();
const authenticationStore = useAuthenticationStore();
const { isInRole } = storeToRefs(authenticationStore);
const { errors, values, meta, defineField } = useForm({
validationSchema: catalogItemSchema,
Expand Down Expand Up @@ -211,7 +216,7 @@ onMounted(async () => {
<button
type="button"
class="rounded bg-blue-600 px-4 py-2 font-bold text-white hover:bg-blue-800 disabled:bg-blue-500 disabled:opacity-50"
:disabled="isInvalid()"
:disabled="isInvalid() || !isInRole(Roles.ADMIN)"
@click="AddItem()"
>
追加
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { storeToRefs } from 'pinia';
import {
fetchItem,
updateCatalogItem,
Expand All @@ -23,8 +24,12 @@ import type {
GetCatalogItemResponse,
} from '@/generated/api-client';
import { useCustomErrorHandler } from '@/shared/error-handler/use-custom-error-handler';
import { useAuthenticationStore } from '@/stores/authentication/authentication';
import { Roles } from '@/shared/constants/roles';
const customErrorHandler = useCustomErrorHandler();
const authenticationStore = useAuthenticationStore();
const { isInRole } = storeToRefs(authenticationStore);
const router = useRouter();
const route = useRoute();
const id = Number(route.params.itemId);
Expand Down Expand Up @@ -238,7 +243,7 @@ const deleteItemAsync = async () => {
} catch (error) {
if (error instanceof NotFoundError) {
customErrorHandler.handle(error, () => {
showToast('更新対象のカタログアイテムが見つかりませんでした');
showToast('削除対象のカタログアイテムが見つかりませんでした');
router.push({ name: '/catalog/items' });
});
} else if (error instanceof ConflictError) {
Expand Down Expand Up @@ -554,9 +559,11 @@ const updateItemAsync = async () => {
/>
</div>
<div class="flex justify-end">
<!-- サンプルアプリは必ず Admin ロールを持つユーザーとしてログインするようになっているので、削除ボタンが disable になることはありません。-->
<button
type="button"
class="rounded bg-red-800 px-4 py-2 font-bold text-white hover:bg-red-900"
class="rounded bg-red-800 px-4 py-2 font-bold text-white hover:bg-red-900 disabled:bg-red-500 disabled:opacity-50"
:disabled="!isInRole(Roles.ADMIN)"
@click="showDeleteConfirm = true"
>
削除
Expand All @@ -565,7 +572,7 @@ const updateItemAsync = async () => {
<button
type="button"
class="rounded bg-blue-600 px-4 py-2 font-bold text-white hover:bg-blue-800 disabled:bg-blue-500 disabled:opacity-50"
:disabled="isInvalid()"
:disabled="isInvalid() || !isInRole(Roles.ADMIN)"
@click="showUpdateConfirm = true"
>
更新
Expand Down
Loading

0 comments on commit c35dee2

Please sign in to comment.