1import * as NodeServices from "@effect/platform-node/NodeServices";
2import { assert, it } from "@effect/vitest";
3import * as Effect from "effect/Effect";
4import * as FileSystem from "effect/FileSystem";
5import * as Path from "effect/Path";
6
7import { applyWebBrandAssets } from "./apply-web-brand-assets.ts";
8
9const WEB_ICON_FILENAMES = [
10 "apple-touch-icon.png",
11 "favicon-16x16.png",
12 "favicon-32x32.png",
13 "favicon.ico",
14 "icon-192x192.png",
15 "icon-512x512.png",
16];
17
18it.layer(NodeServices.layer)("apply-web-brand-assets", (it) => {
19 it.effect("copies the icons into an absolute target directory as given", () =>
20 Effect.gen(function* () {
21 const fs = yield* FileSystem.FileSystem;
22 const targetDirectory = yield* fs.makeTempDirectoryScoped({
23 prefix: "apply-web-brand-assets-",
24 });
25
26 yield* applyWebBrandAssets("production", targetDirectory);
27
28 assert.deepStrictEqual(
29 (yield* fs.readDirectory(targetDirectory)).toSorted(),
30 WEB_ICON_FILENAMES,
31 );
32 }),
33 );
34
35 it.effect("resolves an empty target directory to the repo root", () =>
36 Effect.gen(function* () {
37 const fs = yield* FileSystem.FileSystem;
38 const path = yield* Path.Path;
39 const repoRoot = yield* path.fromFileUrl(new URL("..", import.meta.url));
40 const destinations = WEB_ICON_FILENAMES.map((filename) => path.join(repoRoot, filename));
41 // readDirectory does not follow symlinks, so a dangling link counts as an existing entry.
42 // Names are lowercased so a case-insensitive filesystem cannot hide a differently cased file.
43 const existingEntries = new Set(
44 (yield* fs.readDirectory(repoRoot)).map((entry) => entry.toLowerCase()),
45 );
46 for (const destination of destinations) {
47 assert.isFalse(
48 existingEntries.has(path.basename(destination)),
49 `${destination} already exists`,
50 );
51 }
52 yield* Effect.addFinalizer(() =>
53 Effect.forEach(destinations, (destination) => fs.remove(destination, { force: true })).pipe(
54 Effect.orDie,
55 ),
56 );
57
58 yield* applyWebBrandAssets("production", "");
59
60 for (const destination of destinations) {
61 assert.isTrue(yield* fs.exists(destination));
62 }
63 }),
64 );
65});