diff --git a/.changeset/sharp-lamps-fix.md b/.changeset/sharp-lamps-fix.md new file mode 100644 index 0000000000..52de6690fe --- /dev/null +++ b/.changeset/sharp-lamps-fix.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Add tests for the `useTemplateDirectory` hook. diff --git a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateIntroPage.tsx b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateIntroPage.tsx index b9825bc056..69fcfd37bf 100644 --- a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateIntroPage.tsx +++ b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateIntroPage.tsx @@ -29,7 +29,7 @@ import { } from '../../../routes'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { scaffolderTranslationRef } from '../../../translation'; -import { WebFileSystemStore } from '../../../lib/filesystem/WebFileSystemAccess'; +import { WebFileSystemStore } from '../../../lib/filesystem/WebFileSystemStore'; import { createExampleTemplate } from '../../../lib/filesystem/createExampleTemplate'; export function TemplateIntroPage() { diff --git a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/useTemplateDirectory.test.ts b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/useTemplateDirectory.test.ts new file mode 100644 index 0000000000..2b02785270 --- /dev/null +++ b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/useTemplateDirectory.test.ts @@ -0,0 +1,133 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { act, renderHook, waitFor } from '@testing-library/react'; +import { useTemplateDirectory } from './useTemplateDirectory'; +import { + createExampleTemplate, + TemplateDirectoryAccess, + WebFileSystemStore, +} from '../../../lib/filesystem'; +import { + IterableDirectoryHandle, + WebFileSystemAccess, +} from '../../../lib/filesystem/WebFileSystemAccess'; + +jest.mock('../../../lib/filesystem/createExampleTemplate'); + +describe('useTemplateDirectory', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should return undefined when there is no existing directory in the file system store', async () => { + jest.spyOn(WebFileSystemStore, 'getDirectory').mockResolvedValue(undefined); + + const { result } = renderHook(() => useTemplateDirectory()); + + expect(result.current.directory).toBeUndefined(); + expect(result.current.loading).toBeTruthy(); + expect(result.current.error).toBeUndefined(); + + await waitFor(() => expect(result.current.loading).toBeFalsy()); + + expect(result.current.directory).toBeUndefined(); + expect(result.current.error).toBeUndefined(); + }); + + it('should return an access when there is existing directory in the file system store', async () => { + const handle = {} as IterableDirectoryHandle; + + jest.spyOn(WebFileSystemStore, 'getDirectory').mockResolvedValue(handle); + + const { result } = renderHook(() => useTemplateDirectory()); + + await waitFor(() => expect(result.current.loading).toBeFalsy()); + + expect(result.current.directory).toMatchObject({ handle }); + expect(result.current.error).toBeUndefined(); + }); + + it('should handle opening a directory', async () => { + const handle = {}; + jest + .spyOn(WebFileSystemStore, 'getDirectory') + .mockResolvedValue(handle as IterableDirectoryHandle); + const setDirectory = jest + .spyOn(WebFileSystemStore, 'setDirectory') + .mockResolvedValue(undefined); + const requestDirectoryAccess = jest + .spyOn(WebFileSystemAccess, 'requestDirectoryAccess') + .mockResolvedValue(handle as TemplateDirectoryAccess); + + const { result } = renderHook(() => useTemplateDirectory()); + + expect(result.current.directory).toBeUndefined(); + + await act(async () => { + result.current.handleOpenDirectory(); + }); + + expect(requestDirectoryAccess).toHaveBeenCalled(); + expect(setDirectory).toHaveBeenCalledWith(handle); + + await waitFor(() => expect(result.current.directory).toBeDefined()); + }); + + it('should handle creating a directory', async () => { + const handle = {}; + (createExampleTemplate as jest.Mock).mockResolvedValue(handle); + jest + .spyOn(WebFileSystemStore, 'getDirectory') + .mockResolvedValue(handle as IterableDirectoryHandle); + const setDirectory = jest + .spyOn(WebFileSystemStore, 'setDirectory') + .mockResolvedValue(undefined); + const requestDirectoryAccess = jest + .spyOn(WebFileSystemAccess, 'requestDirectoryAccess') + .mockResolvedValue(handle as TemplateDirectoryAccess); + + const { result } = renderHook(() => useTemplateDirectory()); + + await act(async () => { + result.current.handleCreateDirectory(); + }); + + expect(requestDirectoryAccess).toHaveBeenCalled(); + expect(setDirectory).toHaveBeenCalledWith(handle); + expect(createExampleTemplate).toHaveBeenCalledWith(handle); + }); + + it('should handle closing a directory', async () => { + jest.spyOn(WebFileSystemStore, 'getDirectory').mockResolvedValue(undefined); + + const setDirectory = jest + .spyOn(WebFileSystemStore, 'setDirectory') + .mockResolvedValue(undefined); + + const { result } = renderHook(() => useTemplateDirectory()); + + expect(setDirectory).not.toHaveBeenCalled(); + expect(result.current.directory).toBeUndefined(); + + await act(async () => { + result.current.handleCloseDirectory(); + }); + + expect(setDirectory).toHaveBeenCalledWith(undefined); + await waitFor(() => expect(result.current.directory).toBeUndefined()); + }); +}); diff --git a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/useTemplateDirectory.ts b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/useTemplateDirectory.ts index 812a21e437..47b05db872 100644 --- a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/useTemplateDirectory.ts +++ b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/useTemplateDirectory.ts @@ -18,11 +18,11 @@ import { useCallback } from 'react'; import useAsyncRetry from 'react-use/esm/useAsyncRetry'; import { - WebFileSystemAccess, WebFileSystemStore, + WebFileSystemAccess, WebDirectoryAccess, + createExampleTemplate, } from '../../../lib/filesystem'; -import { createExampleTemplate } from '../../../lib/filesystem/createExampleTemplate'; export function useTemplateDirectory(): { directory?: WebDirectoryAccess; diff --git a/plugins/scaffolder/src/lib/filesystem/WebFileSystemAccess.ts b/plugins/scaffolder/src/lib/filesystem/WebFileSystemAccess.ts index cfb0a6c771..c304e7b394 100644 --- a/plugins/scaffolder/src/lib/filesystem/WebFileSystemAccess.ts +++ b/plugins/scaffolder/src/lib/filesystem/WebFileSystemAccess.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { get, set } from 'idb-keyval'; import { TemplateDirectoryAccess, TemplateFileAccess } from './types'; type WritableFileHandle = FileSystemFileHandle & { @@ -25,7 +24,7 @@ type WritableFileHandle = FileSystemFileHandle & { }; // A nicer type than the one from the TS lib -interface IterableDirectoryHandle extends FileSystemDirectoryHandle { +export interface IterableDirectoryHandle extends FileSystemDirectoryHandle { values(): AsyncIterable< | ({ kind: 'file' } & WritableFileHandle) | ({ kind: 'directory' } & IterableDirectoryHandle) @@ -124,16 +123,3 @@ export class WebFileSystemAccess { private constructor() {} } - -export class WebFileSystemStore { - private static readonly key = 'scalfolder-template-editor-directory'; - - static async getDirectory(): Promise { - const directory = await get(WebFileSystemStore.key); - return directory.handle; - } - - static async setDirectory(directory: TemplateDirectoryAccess | undefined) { - return set(WebFileSystemStore.key, directory); - } -} diff --git a/plugins/scaffolder/src/lib/filesystem/WebFileSystemStore.ts b/plugins/scaffolder/src/lib/filesystem/WebFileSystemStore.ts new file mode 100644 index 0000000000..1ea90dcf76 --- /dev/null +++ b/plugins/scaffolder/src/lib/filesystem/WebFileSystemStore.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { get, set } from 'idb-keyval'; +import { TemplateDirectoryAccess } from './types'; +import { IterableDirectoryHandle } from './WebFileSystemAccess'; + +export class WebFileSystemStore { + private static readonly key = 'scalfolder-template-editor-directory'; + + static async getDirectory(): Promise { + const directory = await get(WebFileSystemStore.key); + return directory.handle; + } + + static async setDirectory(directory: TemplateDirectoryAccess | undefined) { + return set(WebFileSystemStore.key, directory); + } +} diff --git a/plugins/scaffolder/src/lib/filesystem/index.ts b/plugins/scaffolder/src/lib/filesystem/index.ts index 4f5c9ff08c..4f44ab1ccd 100644 --- a/plugins/scaffolder/src/lib/filesystem/index.ts +++ b/plugins/scaffolder/src/lib/filesystem/index.ts @@ -14,10 +14,8 @@ * limitations under the License. */ -export type { TemplateFileAccess, TemplateDirectoryAccess } from './types'; export { blobToBase64 } from './helpers'; -export { - WebDirectoryAccess, - WebFileSystemAccess, - WebFileSystemStore, -} from './WebFileSystemAccess'; +export { createExampleTemplate } from './createExampleTemplate'; +export type { TemplateFileAccess, TemplateDirectoryAccess } from './types'; +export { WebFileSystemStore } from './WebFileSystemStore'; +export { WebDirectoryAccess, WebFileSystemAccess } from './WebFileSystemAccess';