backend-test-utils: refactor MockDirectory to be synchronous
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -36,17 +36,17 @@ export function isDockerDisabledForTests(): boolean;
|
||||
|
||||
// @public
|
||||
export class MockDirectory {
|
||||
addContent(root: MockDirectoryContent): Promise<void>;
|
||||
clear: () => Promise<void>;
|
||||
addContent(root: MockDirectoryContent): void;
|
||||
clear: () => void;
|
||||
content(
|
||||
options?: MockDirectoryContentOptions,
|
||||
): Promise<MockDirectoryContent | undefined>;
|
||||
): MockDirectoryContent | undefined;
|
||||
static create(options?: MockDirectoryCreateOptions): MockDirectory;
|
||||
static mockOsTmpDir(): MockDirectory;
|
||||
get path(): string;
|
||||
remove: () => Promise<void>;
|
||||
remove: () => void;
|
||||
resolve(...paths: string[]): string;
|
||||
setContent(root: MockDirectoryContent): Promise<void>;
|
||||
setContent(root: MockDirectoryContent): void;
|
||||
}
|
||||
|
||||
// @public
|
||||
|
||||
@@ -38,19 +38,19 @@ describe('MockDirectory', () => {
|
||||
|
||||
it('should remove itself', async () => {
|
||||
await expect(fs.pathExists(mockDir.path)).resolves.toBe(true);
|
||||
await mockDir.remove();
|
||||
mockDir.remove();
|
||||
await expect(fs.pathExists(mockDir.path)).resolves.toBe(false);
|
||||
});
|
||||
|
||||
it('should populate a directory with text files', async () => {
|
||||
await mockDir.setContent({
|
||||
it('should populate a directory with text files', () => {
|
||||
mockDir.setContent({
|
||||
'a.txt': 'a',
|
||||
'a/b.txt': 'b',
|
||||
'a/b/c.txt': 'c',
|
||||
'a/b/d.txt': 'd',
|
||||
});
|
||||
|
||||
await expect(mockDir.content()).resolves.toEqual({
|
||||
expect(mockDir.content()).toEqual({
|
||||
'a.txt': 'a',
|
||||
a: {
|
||||
'b.txt': 'b',
|
||||
@@ -62,15 +62,15 @@ describe('MockDirectory', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should mix text and binary files', async () => {
|
||||
await mockDir.setContent({
|
||||
it('should mix text and binary files', () => {
|
||||
mockDir.setContent({
|
||||
'a.txt': 'a',
|
||||
'a/b.txt': 'b',
|
||||
'a/b/c.bin': Buffer.from([0xc]),
|
||||
'a/b/d.bin': Buffer.from([0xd]),
|
||||
});
|
||||
|
||||
await expect(mockDir.content()).resolves.toEqual({
|
||||
expect(mockDir.content()).toEqual({
|
||||
'a.txt': 'a',
|
||||
a: {
|
||||
'b.txt': 'b',
|
||||
@@ -82,25 +82,25 @@ describe('MockDirectory', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to add content', async () => {
|
||||
await mockDir.setContent({
|
||||
it('should be able to add content', () => {
|
||||
mockDir.setContent({
|
||||
'a.txt': 'a',
|
||||
b: {},
|
||||
});
|
||||
|
||||
await expect(mockDir.content()).resolves.toEqual({
|
||||
expect(mockDir.content()).toEqual({
|
||||
'a.txt': 'a',
|
||||
b: {},
|
||||
});
|
||||
|
||||
await mockDir.addContent({
|
||||
mockDir.addContent({
|
||||
'b.txt': 'b',
|
||||
b: {
|
||||
'c.txt': 'c',
|
||||
},
|
||||
});
|
||||
|
||||
await expect(mockDir.content()).resolves.toEqual({
|
||||
expect(mockDir.content()).toEqual({
|
||||
'a.txt': 'a',
|
||||
'b.txt': 'b',
|
||||
b: {
|
||||
@@ -109,22 +109,22 @@ describe('MockDirectory', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should replace existing files', async () => {
|
||||
await mockDir.setContent({
|
||||
it('should replace existing files', () => {
|
||||
mockDir.setContent({
|
||||
'a.txt': 'a',
|
||||
});
|
||||
|
||||
await mockDir.addContent({
|
||||
mockDir.addContent({
|
||||
'a.txt': 'a2',
|
||||
});
|
||||
|
||||
await expect(mockDir.content()).resolves.toEqual({
|
||||
expect(mockDir.content()).toEqual({
|
||||
'a.txt': 'a2',
|
||||
});
|
||||
});
|
||||
|
||||
it('should read content from sub dirs', async () => {
|
||||
await mockDir.setContent({
|
||||
it('should read content from sub dirs', () => {
|
||||
mockDir.setContent({
|
||||
'a.txt': 'a',
|
||||
'b/b.txt': 'b',
|
||||
'b/c/c.txt': 'c',
|
||||
@@ -140,77 +140,65 @@ describe('MockDirectory', () => {
|
||||
},
|
||||
};
|
||||
|
||||
await expect(mockDir.content()).resolves.toEqual(expected);
|
||||
await expect(mockDir.content({ path: mockDir.path })).resolves.toEqual(
|
||||
expected,
|
||||
);
|
||||
await expect(
|
||||
mockDir.content({ path: mockDir.resolve('.') }),
|
||||
).resolves.toEqual(expected);
|
||||
await expect(mockDir.content({ path: 'b' })).resolves.toEqual(expected.b);
|
||||
await expect(mockDir.content({ path: './b' })).resolves.toEqual(expected.b);
|
||||
await expect(
|
||||
mockDir.content({ path: mockDir.resolve('b') }),
|
||||
).resolves.toEqual(expected.b);
|
||||
await expect(mockDir.content({ path: 'b/c' })).resolves.toEqual(
|
||||
expect(mockDir.content()).toEqual(expected);
|
||||
expect(mockDir.content({ path: mockDir.path })).toEqual(expected);
|
||||
expect(mockDir.content({ path: mockDir.resolve('.') })).toEqual(expected);
|
||||
expect(mockDir.content({ path: 'b' })).toEqual(expected.b);
|
||||
expect(mockDir.content({ path: './b' })).toEqual(expected.b);
|
||||
expect(mockDir.content({ path: mockDir.resolve('b') })).toEqual(expected.b);
|
||||
expect(mockDir.content({ path: 'b/c' })).toEqual(expected.b.c);
|
||||
expect(mockDir.content({ path: './b/c' })).toEqual(expected.b.c);
|
||||
expect(mockDir.content({ path: mockDir.resolve('b/c') })).toEqual(
|
||||
expected.b.c,
|
||||
);
|
||||
await expect(mockDir.content({ path: './b/c' })).resolves.toEqual(
|
||||
expect(mockDir.content({ path: mockDir.resolve('b', 'c') })).toEqual(
|
||||
expected.b.c,
|
||||
);
|
||||
await expect(
|
||||
mockDir.content({ path: mockDir.resolve('b/c') }),
|
||||
).resolves.toEqual(expected.b.c);
|
||||
await expect(
|
||||
mockDir.content({ path: mockDir.resolve('b', 'c') }),
|
||||
).resolves.toEqual(expected.b.c);
|
||||
});
|
||||
|
||||
it('should allow text reading to be configured', async () => {
|
||||
it('should allow text reading to be configured', () => {
|
||||
const text = 'a';
|
||||
const binary = Buffer.from('a', 'utf8');
|
||||
|
||||
await mockDir.setContent({
|
||||
mockDir.setContent({
|
||||
a: binary,
|
||||
'a.txt': text,
|
||||
'a.bin': binary,
|
||||
});
|
||||
|
||||
await expect(mockDir.content()).resolves.toEqual({
|
||||
expect(mockDir.content()).toEqual({
|
||||
a: binary,
|
||||
'a.txt': text,
|
||||
'a.bin': binary,
|
||||
});
|
||||
|
||||
await expect(mockDir.content({ shouldReadAsText: false })).resolves.toEqual(
|
||||
{
|
||||
a: binary,
|
||||
'a.txt': binary,
|
||||
'a.bin': binary,
|
||||
},
|
||||
);
|
||||
expect(mockDir.content({ shouldReadAsText: false })).toEqual({
|
||||
a: binary,
|
||||
'a.txt': binary,
|
||||
'a.bin': binary,
|
||||
});
|
||||
|
||||
await expect(mockDir.content({ shouldReadAsText: true })).resolves.toEqual({
|
||||
expect(mockDir.content({ shouldReadAsText: true })).toEqual({
|
||||
a: text,
|
||||
'a.txt': text,
|
||||
'a.bin': text,
|
||||
});
|
||||
|
||||
await expect(
|
||||
expect(
|
||||
mockDir.content({ shouldReadAsText: path => path.length > 3 }),
|
||||
).resolves.toEqual({
|
||||
).toEqual({
|
||||
a: binary,
|
||||
'a.txt': text,
|
||||
'a.bin': text,
|
||||
});
|
||||
});
|
||||
|
||||
it('should provide a posix path to shouldReadAsText', async () => {
|
||||
it('should provide a posix path to shouldReadAsText', () => {
|
||||
const shouldReadAsText = jest.fn().mockReturnValue(true);
|
||||
|
||||
await mockDir.setContent({ 'a/b/c': 'c' });
|
||||
mockDir.setContent({ 'a/b/c': 'c' });
|
||||
|
||||
await expect(mockDir.content({ shouldReadAsText })).resolves.toEqual({
|
||||
expect(mockDir.content({ shouldReadAsText })).toEqual({
|
||||
a: { b: { c: 'c' } },
|
||||
});
|
||||
expect(shouldReadAsText).toHaveBeenCalledWith(
|
||||
@@ -219,27 +207,27 @@ describe('MockDirectory', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should not override directories', async () => {
|
||||
await mockDir.setContent({
|
||||
it('should not override directories', () => {
|
||||
mockDir.setContent({
|
||||
'a.txt': 'a',
|
||||
b: {},
|
||||
});
|
||||
|
||||
await expect(
|
||||
expect(() =>
|
||||
mockDir.addContent({
|
||||
'a.txt': {},
|
||||
}),
|
||||
).rejects.toThrow('EEXIST');
|
||||
).toThrow('EEXIST');
|
||||
|
||||
await expect(
|
||||
expect(() =>
|
||||
mockDir.addContent({
|
||||
b: 'b',
|
||||
}),
|
||||
).rejects.toThrow('EISDIR');
|
||||
).toThrow('EISDIR');
|
||||
});
|
||||
|
||||
it('examples should work', async () => {
|
||||
await mockDir.setContent({
|
||||
it('examples should work', () => {
|
||||
mockDir.setContent({
|
||||
'test.txt': 'content',
|
||||
'sub-dir': {
|
||||
'file.txt': 'content',
|
||||
@@ -249,7 +237,7 @@ describe('MockDirectory', () => {
|
||||
'binary-file': Buffer.from([0, 1, 2]),
|
||||
});
|
||||
|
||||
await mockDir.addContent({
|
||||
mockDir.addContent({
|
||||
'test.txt': 'content',
|
||||
'sub-dir': {
|
||||
'file.txt': 'content',
|
||||
@@ -259,7 +247,7 @@ describe('MockDirectory', () => {
|
||||
'binary-file': Buffer.from([0, 1, 2]),
|
||||
});
|
||||
|
||||
await expect(mockDir.content()).resolves.toEqual({
|
||||
expect(mockDir.content()).toEqual({
|
||||
'test.txt': 'content',
|
||||
'sub-dir': {
|
||||
'file.txt': 'content',
|
||||
@@ -272,15 +260,15 @@ describe('MockDirectory', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject non-child paths', async () => {
|
||||
it('should reject non-child paths', () => {
|
||||
const path = resolvePath('/root/a.txt');
|
||||
await expect(mockDir.setContent({ '/root/a.txt': 'a' })).rejects.toThrow(
|
||||
expect(() => mockDir.setContent({ '/root/a.txt': 'a' })).toThrow(
|
||||
`Provided path must resolve to a child path of the mock directory, got '${path}'`,
|
||||
);
|
||||
await expect(mockDir.addContent({ '/root/a.txt': 'a' })).rejects.toThrow(
|
||||
expect(() => mockDir.addContent({ '/root/a.txt': 'a' })).toThrow(
|
||||
`Provided path must resolve to a child path of the mock directory, got '${path}'`,
|
||||
);
|
||||
await expect(mockDir.content({ path: '/root/a.txt' })).rejects.toThrow(
|
||||
expect(() => mockDir.content({ path: '/root/a.txt' })).toThrow(
|
||||
`Provided path must resolve to a child path of the mock directory, got '${path}'`,
|
||||
);
|
||||
});
|
||||
@@ -291,19 +279,19 @@ describe('MockDirectory', () => {
|
||||
describe('inner', () => {
|
||||
cleanupMockDir = MockDirectory.create();
|
||||
|
||||
it('should populate a directory', async () => {
|
||||
await cleanupMockDir.setContent({
|
||||
it('should populate a directory', () => {
|
||||
cleanupMockDir.setContent({
|
||||
'a.txt': 'a',
|
||||
});
|
||||
|
||||
await expect(cleanupMockDir.content()).resolves.toEqual({
|
||||
expect(cleanupMockDir.content()).toEqual({
|
||||
'a.txt': 'a',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should clean up after itself automatically', async () => {
|
||||
await expect(cleanupMockDir.content()).resolves.toBeUndefined();
|
||||
it('should clean up after itself automatically', () => {
|
||||
expect(cleanupMockDir.content()).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -313,12 +301,12 @@ describe('MockDirectory', () => {
|
||||
describe('inner', () => {
|
||||
tmpDirMock = MockDirectory.mockOsTmpDir();
|
||||
|
||||
it('should mock os.tmpdir()', async () => {
|
||||
it('should mock os.tmpdir()', () => {
|
||||
expect(os.tmpdir()).toBe(tmpDirMock.path);
|
||||
});
|
||||
});
|
||||
|
||||
it('should restore os.tmpdir()', async () => {
|
||||
it('should restore os.tmpdir()', () => {
|
||||
expect(os.tmpdir()).not.toBe(tmpDirMock.path);
|
||||
});
|
||||
});
|
||||
@@ -329,15 +317,15 @@ describe('MockDirectory', () => {
|
||||
describe('inner', () => {
|
||||
existingMockDir = MockDirectory.create({ root: __dirname }); // hardcore mode
|
||||
|
||||
it('should read existing directory', async () => {
|
||||
await expect(existingMockDir.content()).resolves.toMatchObject({
|
||||
it('should read existing directory', () => {
|
||||
expect(existingMockDir.content()).toMatchObject({
|
||||
'index.ts': expect.any(String),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove existing directory', async () => {
|
||||
await expect(fs.pathExists(__dirname)).resolves.toBe(true);
|
||||
it('should remove existing directory', () => {
|
||||
expect(fs.pathExistsSync(__dirname)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -124,9 +124,9 @@ export class MockDirectory {
|
||||
* describe('MySubject', () => {
|
||||
* const mockDir = MockDirectory.create();
|
||||
*
|
||||
* beforeEach(() => mockDir.clear());
|
||||
* beforeEach(mockDir.clear);
|
||||
*
|
||||
* it('should work', async () => {
|
||||
* it('should work', () => {
|
||||
* // ... use mockDir
|
||||
* })
|
||||
* })
|
||||
@@ -141,7 +141,7 @@ export class MockDirectory {
|
||||
|
||||
const shouldCleanup = !options?.root || !fs.pathExistsSync(options.root);
|
||||
if (shouldCleanup) {
|
||||
process.on('beforeExit', mocker.#removeSync);
|
||||
process.on('beforeExit', mocker.remove);
|
||||
|
||||
try {
|
||||
afterAll(mocker.remove);
|
||||
@@ -199,7 +199,7 @@ export class MockDirectory {
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* await mockDir.setContent({
|
||||
* mockDir.setContent({
|
||||
* 'test.txt': 'content',
|
||||
* 'sub-dir': {
|
||||
* 'file.txt': 'content',
|
||||
@@ -210,8 +210,8 @@ export class MockDirectory {
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
async setContent(root: MockDirectoryContent): Promise<void> {
|
||||
await this.remove();
|
||||
setContent(root: MockDirectoryContent): void {
|
||||
this.remove();
|
||||
|
||||
return this.addContent(root);
|
||||
}
|
||||
@@ -221,7 +221,7 @@ export class MockDirectory {
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* await mockDir.addContent({
|
||||
* mockDir.addContent({
|
||||
* 'test.txt': 'content',
|
||||
* 'sub-dir': {
|
||||
* 'file.txt': 'content',
|
||||
@@ -232,7 +232,7 @@ export class MockDirectory {
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
async addContent(root: MockDirectoryContent): Promise<void> {
|
||||
addContent(root: MockDirectoryContent): void {
|
||||
const entries = this.#transformInput(root);
|
||||
|
||||
for (const entry of entries) {
|
||||
@@ -244,10 +244,10 @@ export class MockDirectory {
|
||||
}
|
||||
|
||||
if (entry.type === 'dir') {
|
||||
await fs.ensureDir(fullPath);
|
||||
fs.ensureDirSync(fullPath);
|
||||
} else if (entry.type === 'file') {
|
||||
await fs.ensureDir(dirname(fullPath));
|
||||
await fs.writeFile(fullPath, entry.content);
|
||||
fs.ensureDirSync(dirname(fullPath));
|
||||
fs.writeFileSync(fullPath, entry.content);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -262,7 +262,7 @@ export class MockDirectory {
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* await expect(mockDir.content()).resolves.toEqual({
|
||||
* expect(mockDir.content()).toEqual({
|
||||
* 'test.txt': 'content',
|
||||
* 'sub-dir': {
|
||||
* 'file.txt': 'content',
|
||||
@@ -275,9 +275,9 @@ export class MockDirectory {
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
async content(
|
||||
content(
|
||||
options?: MockDirectoryContentOptions,
|
||||
): Promise<MockDirectoryContent | undefined> {
|
||||
): MockDirectoryContent | undefined {
|
||||
const shouldReadAsText =
|
||||
(typeof options?.shouldReadAsText === 'boolean'
|
||||
? () => options?.shouldReadAsText
|
||||
@@ -291,33 +291,29 @@ export class MockDirectory {
|
||||
);
|
||||
}
|
||||
|
||||
async function read(
|
||||
path: string,
|
||||
): Promise<MockDirectoryContent | undefined> {
|
||||
if (!(await fs.pathExists(path))) {
|
||||
function read(path: string): MockDirectoryContent | undefined {
|
||||
if (!fs.pathExistsSync(path)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const entries = await fs.readdir(path, { withFileTypes: true });
|
||||
const entries = fs.readdirSync(path, { withFileTypes: true });
|
||||
return Object.fromEntries(
|
||||
await Promise.all(
|
||||
entries.map(async entry => {
|
||||
const fullPath = resolvePath(path, entry.name);
|
||||
entries.map(entry => {
|
||||
const fullPath = resolvePath(path, entry.name);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
return [entry.name, await read(fullPath)];
|
||||
}
|
||||
const content = await fs.readFile(fullPath);
|
||||
const relativePosixPath = relativePath(root, fullPath)
|
||||
.split(win32.sep)
|
||||
.join(posix.sep);
|
||||
if (entry.isDirectory()) {
|
||||
return [entry.name, read(fullPath)];
|
||||
}
|
||||
const content = fs.readFileSync(fullPath);
|
||||
const relativePosixPath = relativePath(root, fullPath)
|
||||
.split(win32.sep)
|
||||
.join(posix.sep);
|
||||
|
||||
if (shouldReadAsText(relativePosixPath, content)) {
|
||||
return [entry.name, content.toString('utf8')];
|
||||
}
|
||||
return [entry.name, content];
|
||||
}),
|
||||
),
|
||||
if (shouldReadAsText(relativePosixPath, content)) {
|
||||
return [entry.name, content.toString('utf8')];
|
||||
}
|
||||
return [entry.name, content];
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -327,20 +323,20 @@ export class MockDirectory {
|
||||
/**
|
||||
* Clears the content of the mock directory, ensuring that the directory itself exists.
|
||||
*/
|
||||
clear = async (): Promise<void> => {
|
||||
await this.setContent({});
|
||||
clear = (): void => {
|
||||
this.setContent({});
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the mock directory and all its contents.
|
||||
*/
|
||||
remove = async (): Promise<void> => {
|
||||
remove = (): void => {
|
||||
try {
|
||||
await fs.rm(this.#root, { recursive: true, force: true });
|
||||
fs.rmSync(this.#root, { recursive: true, force: true });
|
||||
} catch (error: unknown) {
|
||||
if (isError(error) && error.code === 'ENOTEMPTY') {
|
||||
// Windows can be a bit flaky, give it another go
|
||||
await fs.rm(this.#root, { recursive: true, force: true });
|
||||
fs.rmSync(this.#root, { recursive: true, force: true });
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
@@ -372,8 +368,4 @@ export class MockDirectory {
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
#removeSync = () => {
|
||||
fs.rmSync(this.#root, { recursive: true, force: true });
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user