Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] エンジンのモック作成+それを使ったコンポーネントテスト #2152

Draft
wants to merge 39 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
34dcfc2
stash
Hiroshiba Jun 30, 2024
c05f3b6
scssが読み込めない!!!
Hiroshiba Jul 2, 2024
b482e48
stash
Hiroshiba Jul 2, 2024
0168fb7
Merge remote-tracking branch 'upstream/main' into エンジンのmockを作る
Hiroshiba Jul 6, 2024
409ca08
mainに近づける
Hiroshiba Jul 6, 2024
1712692
テーマ周りをリファクタリング
Hiroshiba Jul 6, 2024
b1b10e4
stash
Hiroshiba Jul 7, 2024
0631c14
なぜかspec.ts側でStoryが動かせない・・・
Hiroshiba Jul 27, 2024
79d4a66
Merge remote-tracking branch 'upstream/main' into エンジンのmockを作る
Hiroshiba Aug 3, 2024
602fdd4
スナップショットできた!
Hiroshiba Aug 3, 2024
e29a8f4
8.2.7
Hiroshiba Aug 3, 2024
4d16516
typo
Hiroshiba Aug 3, 2024
aa38200
いらなかった
Hiroshiba Aug 3, 2024
36382bd
ThemeConf[]
Hiroshiba Aug 3, 2024
1240838
トーク音声を再生できるように
Hiroshiba Aug 10, 2024
4d4524e
addActionsWithEmitsを削除
Hiroshiba Aug 10, 2024
518354c
snapshot更新
Hiroshiba Aug 10, 2024
067a356
Merge branch 'main' into エンジンのmockを作る
Hiroshiba Aug 11, 2024
c83f5cb
ちょっと型調整
Hiroshiba Aug 11, 2024
2009e2d
stash
Hiroshiba Aug 12, 2024
a47e3a3
Merge remote-tracking branch 'upstream/main' into エンジンのmockを作る
Hiroshiba Aug 17, 2024
d7eee81
stash
Hiroshiba Aug 18, 2024
2bc525f
Merge remote-tracking branch 'upstream/main' into エンジンのmockを作る
Hiroshiba Aug 24, 2024
315fff6
openapi更新
Hiroshiba Aug 24, 2024
37c17ca
たぶんsing engineのモックができた、SingEditorのstoriesを作って試す
Hiroshiba Aug 24, 2024
208d9a5
stash
Hiroshiba Aug 24, 2024
cc641d6
Merge remote-tracking branch 'upstream/main' into エンジンのmockを作る
Hiroshiba Aug 31, 2024
3134578
package-lockをもどす
Hiroshiba Aug 31, 2024
97b8e1f
package-lockを最新へ
Hiroshiba Aug 31, 2024
0dd1d5f
忘れてた
Hiroshiba Aug 31, 2024
f83ddf2
定数を移動
Hiroshiba Aug 31, 2024
5a80811
TODO: SingEditorの縦幅を変わらないようにする
Hiroshiba Aug 31, 2024
52c3e72
Merge remote-tracking branch 'upstream/main' into エンジンのmockを作る
Hiroshiba Sep 7, 2024
cdec85a
エディタ表示できた!
Hiroshiba Sep 7, 2024
5b706a5
たぶんドラッグができなくてテスト不可能
Hiroshiba Sep 7, 2024
9758fd8
Merge remote-tracking branch 'upstream/main' into エンジンのmockを作る
Hiroshiba Sep 15, 2024
2ddde64
storybookへの追加やめる
Hiroshiba Sep 15, 2024
5c58de9
忘れてた
Hiroshiba Sep 15, 2024
24cde26
stash。GENERATE_AUDIO_ITEMを解体するか、モックを刺す。
Hiroshiba Sep 15, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .storybook/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const preview: Preview = {
],
},
},

decorators: [
withThemeByDataAttribute({
themes: {
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ Storybook を使ってコンポーネントを開発することができます
npm run storybook
```

また、main ブランチの Storybook が Chromatic から見れます。
<https://main--667d9c007418420dbb5b0f75.chromatic.com/>

### ブラウザ版の実行(開発中)

別途音声合成エンジンを起動し、以下を実行して表示された localhost へアクセスします。
Expand All @@ -96,7 +99,7 @@ npm run storybook
npm run browser:serve
```

また、main ブランチのビルド結果がこちらにデプロイされています <https://voicevox-browser-dev.netlify.app/>
また、main ブランチのビルド結果がこちらにデプロイされています <https://voicevox-browser-dev.netlify.app/>
今はローカル PC 上で音声合成エンジンを起動する必要があります。

## ビルド
Expand Down
2 changes: 1 addition & 1 deletion openapi.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
"@types/async-lock": "1.4.0",
"@types/encoding-japanese": "1.0.18",
"@types/glob": "8.0.0",
"@types/kuromoji": "0.1.3",
"@types/markdown-it": "12.2.0",
"@types/multistream": "4.1.0",
"@types/semver": "7.3.9",
Expand Down
23 changes: 3 additions & 20 deletions src/backend/browser/sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,32 +295,15 @@ export const api: Sandbox = {
// TODO: Impl
return;
},
async theme(newData?: string) {
if (newData != undefined) {
await this.setSetting("currentTheme", newData);
return;
}
async getAvailableThemes() {
// NOTE: Electron版では起動時にテーマ情報が必要なので、
// この実装とは違って起動時に読み込んだキャッシュを返すだけになっている。
return Promise.all(
// FIXME: themeファイルのいい感じのパスの設定
["/themes/default.json", "/themes/dark.json"].map((url) =>
fetch(url).then((res) => res.json()),
fetch(url).then((res) => res.json() as Promise<ThemeConf>),
),
)
.then((v) => ({
currentTheme: "Default",
availableThemes: v,
}))
.then((v) =>
this.getSetting("currentTheme").then(
(currentTheme) =>
({
...v,
currentTheme,
}) as { currentTheme: string; availableThemes: ThemeConf[] },
),
);
);
},
vuexReady() {
// NOTE: 何もしなくて良さそう
Expand Down
15 changes: 4 additions & 11 deletions src/backend/electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -906,6 +906,10 @@ registerIpcMainHandle<IpcMainHandle>({
}
},

GET_AVAILABLE_THEMES: () => {
return themes;
},

OPEN_LOG_DIRECTORY: () => {
void shell.openPath(app.getPath("logs"));
},
Expand Down Expand Up @@ -946,17 +950,6 @@ registerIpcMainHandle<IpcMainHandle>({
return configManager.get("hotkeySettings");
},

THEME: (_, { newData }) => {
if (newData != undefined) {
configManager.set("currentTheme", newData);
return;
}
return {
currentTheme: configManager.get("currentTheme"),
availableThemes: themes,
};
},

ON_VUEX_READY: () => {
win.show();
},
Expand Down
4 changes: 2 additions & 2 deletions src/backend/electron/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,8 @@ const api: Sandbox = {
void ipcRendererInvokeProxy.SET_NATIVE_THEME(source);
},

theme: (newData) => {
return ipcRendererInvokeProxy.THEME({ newData });
getAvailableThemes: () => {
return ipcRendererInvokeProxy.GET_AVAILABLE_THEMES();
},

vuexReady: () => {
Expand Down
30 changes: 29 additions & 1 deletion src/components/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import AllDialog from "@/components/Dialog/AllDialog.vue";
import MenuBar from "@/components/Menu/MenuBar/MenuBar.vue";
import { useMenuBarData as useTalkMenuBarData } from "@/components/Talk/menuBarData";
import { useMenuBarData as useSingMenuBarData } from "@/components/Sing/menuBarData";
import { setFont, themeToCss } from "@/domain/dom";
import { ExhaustiveError } from "@/type/utility";

const store = useStore();
Expand Down Expand Up @@ -69,7 +70,7 @@ watch(
watch(
() => store.state.editorFont,
(editorFont) => {
document.body.setAttribute("data-editor-font", editorFont);
setFont(editorFont);
},
{ immediate: true },
);
Expand All @@ -84,6 +85,33 @@ watch(
},
);

// テーマの変更を監視してCSS変数を変更する
watch(
() =>
[
store.state.currentTheme,
store.state.availableThemes,
store.state.isVuexReady,
] as const,
([currentTheme, availableThemes, isVuexReady]) => {
const theme = availableThemes.find((value) => {
return value.name == currentTheme;
});

if (theme == undefined) {
// NOTE: Vuexが初期化されていない場合はまだテーマが読み込まれていないので無視
if (isVuexReady) {
throw Error(`Theme not found: ${currentTheme}`);
} else {
return;
}
}

themeToCss(theme);
},
{ immediate: true },
);

// ソフトウェアを初期化
const { hotkeyManager } = useHotkeyManager();
const isEnginesReady = ref(false);
Expand Down
6 changes: 3 additions & 3 deletions src/components/Dialog/SettingDialog/SettingDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -678,14 +678,14 @@ const undoableTrackOperations = computed({

// 外観
const currentThemeNameComputed = computed({
get: () => store.state.themeSetting.currentTheme,
get: () => store.state.currentTheme,
set: (currentTheme: string) => {
void store.dispatch("SET_THEME_SETTING", { currentTheme: currentTheme });
void store.dispatch("SET_CURRENT_THEME_SETTING", { currentTheme });
},
});

const availableThemeNameComputed = computed(() => {
return [...store.state.themeSetting.availableThemes]
return [...store.state.availableThemes]
.sort((a, b) => a.order - b.order)
.map((theme) => {
return { label: theme.displayName, value: theme.name };
Expand Down
10 changes: 7 additions & 3 deletions src/components/Sing/ScoreSequencer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,10 @@ import { applyGaussianFilter, linearInterpolation } from "@/sing/utility";
import { useLyricInput } from "@/composables/useLyricInput";
import { ExhaustiveError } from "@/type/utility";
import { uuid4 } from "@/helpers/random";
import {
onMountedOrActivated,
onUnmountedOrDeactivated,
} from "@/composables/onMountOrActivate";

type PreviewMode =
| "ADD_NOTE"
Expand Down Expand Up @@ -1333,7 +1337,7 @@ onMounted(() => {
let firstActivation = true;

// スクロール位置を設定する
onActivated(() => {
onMountedOrActivated(() => {
const sequencerBodyElement = sequencerBody.value;
if (!sequencerBodyElement) {
throw new Error("sequencerBodyElement is null.");
Expand Down Expand Up @@ -1362,7 +1366,7 @@ onActivated(() => {
});

// リスナー登録
onActivated(() => {
onMountedOrActivated(() => {
void store.dispatch("ADD_PLAYHEAD_POSITION_CHANGE_LISTENER", {
listener: playheadPositionChangeListener,
});
Expand All @@ -1371,7 +1375,7 @@ onActivated(() => {
});

// リスナー解除
onDeactivated(() => {
onUnmountedOrDeactivated(() => {
void store.dispatch("REMOVE_PLAYHEAD_POSITION_CHANGE_LISTENER", {
listener: playheadPositionChangeListener,
});
Expand Down
179 changes: 179 additions & 0 deletions src/components/Sing/SingEditor.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import {
userEvent,
within,
expect,
fn,
waitFor,
fireEvent,
} from "@storybook/test";

import { Meta, StoryObj } from "@storybook/vue3";
import { provide, toRaw } from "vue";
import SingEditor from "./SingEditor.vue";
import { createStoreWrapper, storeKey } from "@/store";
import { HotkeyManager, hotkeyManagerKey } from "@/plugins/hotkeyPlugin";
import { createOpenAPIEngineMock, mockHost } from "@/mock/engineMock";
import { proxyStoreCreator } from "@/store/proxy";
import {
CharacterInfo,
defaultHotkeySettings,
DefaultStyleId,
EngineId,
EngineInfo,
SpeakerId,
StyleId,
ThemeConf,
} from "@/type/preload";
import { getEngineManifestMock } from "@/mock/engineMock/manifestMock";
import {
getSpeakerInfoMock,
getSpeakersMock,
} from "@/mock/engineMock/speakerResourceMock";
import { setFont, themeToCss } from "@/domain/dom";
import defaultTheme from "@/../public/themes/default.json";
import { cloneWithUnwrapProxy } from "@/helpers/cloneWithUnwrapProxy";
import { assetsPath } from "@/mock/engineMock/constants";

const meta: Meta<typeof SingEditor> = {
component: SingEditor,
args: {
isEnginesReady: true,
isProjectFileLoaded: false,
onCompleteInitialStartup: fn(),
},
decorators: [
(story, context) => {
// CSS関連
themeToCss(defaultTheme as ThemeConf);
setFont("default");

// ショートカットキーの管理
const hotkeyManager = new HotkeyManager();
provide(hotkeyManagerKey, hotkeyManager);
hotkeyManager.load(defaultHotkeySettings);

hotkeyManager.onEditorChange("talk");

// setup store
const store = createStoreWrapper({
proxyStoreDI: proxyStoreCreator(createOpenAPIEngineMock()),
});
provide(storeKey, store);

// なぜか必要、これがないとdispatch内でcommitしたときにエラーになる
store.replaceState({
...cloneWithUnwrapProxy(store.state),
});

context.parameters.vuexState = store.state;

// エンジンの情報
const engineManifest = getEngineManifestMock();
const engineId = EngineId(engineManifest.uuid);
const engineInfo: EngineInfo = {
uuid: engineId,
host: mockHost,
name: engineManifest.name,
path: undefined,
executionEnabled: false,
executionFilePath: "not_found",
executionArgs: [],
isDefault: true,
type: "path",
};
store.commit("SET_ENGINE_INFOS", {
engineIds: [engineId],
engineInfos: [engineInfo],
});
store.commit("SET_ENGINE_MANIFESTS", {
engineManifests: { [engineId]: engineManifest },
});
store.commit("SET_ENGINE_SETTING", {
engineId,
engineSetting: {
outputSamplingRate: engineManifest.defaultSamplingRate,
useGpu: false,
},
});
store.commit("SET_ENGINE_STATE", { engineId, engineState: "READY" });

// キャラクター情報
const speakers = getSpeakersMock();
const characterInfos: CharacterInfo[] = speakers.map((speaker) => {
const speakerInfo = getSpeakerInfoMock(speaker.speakerUuid, assetsPath);
return {
portraitPath: speakerInfo.portrait,
metas: {
speakerUuid: SpeakerId(speaker.speakerUuid),
speakerName: speaker.name,
styles: speakerInfo.styleInfos.map((styleInfo) => {
const style = speaker.styles.find((s) => s.id === styleInfo.id);
if (style == undefined) throw new Error("style not found");
return {
styleName: style.name,
styleId: StyleId(style.id),
styleType: style.type,
iconPath: styleInfo.icon,
portraitPath: styleInfo.portrait ?? speakerInfo.portrait,
engineId,
voiceSamplePaths: styleInfo.voiceSamples,
};
}),
policy: speakerInfo.policy,
},
};
});
store.commit("SET_CHARACTER_INFOS", { engineId, characterInfos });
store.commit("SET_USER_CHARACTER_ORDER", {
userCharacterOrder: store.state.characterInfos[engineId].map(
(c) => c.metas.speakerUuid,
),
});

// デフォルトスタイルID
const defaultStyleIds: DefaultStyleId[] = speakers.map((speaker) => ({
engineId: engineId,
speakerUuid: SpeakerId(speaker.speakerUuid),
defaultStyleId: StyleId(speaker.styles[0].id),
}));
store.commit("SET_DEFAULT_STYLE_IDS", { defaultStyleIds });

return story();
},
],
};

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
name: "デフォルト",
play: async ({ args }) => {
// 準備が完了するまで待機する
await waitFor(
() => expect(args.onCompleteInitialStartup).toHaveBeenCalled(),
{ timeout: 5000 },
);
},
};

export const NowLoading: Story = {
name: "プロジェクトファイルを読み込み中",
args: {
isProjectFileLoaded: "waiting",
},
};

export const AddNote: Story = {
name: "ノート追加のテスト",
play: async ({ context, canvasElement, parameters }) => {
await Default.play?.(context);

const canvas = within(canvasElement);

// たぶんドラッグができなくてテスト不可能

// const { audioItems, audioKeys } = parameters.vuexState;
// await window.storybookTestSnapshot?.({ audioItems, audioKeys });
},
};
Loading
Loading