summaryrefslogtreecommitdiff
path: root/packages/astro/test/astro-sync.test.js
diff options
context:
space:
mode:
Diffstat (limited to 'packages/astro/test/astro-sync.test.js')
-rw-r--r--packages/astro/test/astro-sync.test.js248
1 files changed, 248 insertions, 0 deletions
diff --git a/packages/astro/test/astro-sync.test.js b/packages/astro/test/astro-sync.test.js
new file mode 100644
index 000000000..3e5a9bcbf
--- /dev/null
+++ b/packages/astro/test/astro-sync.test.js
@@ -0,0 +1,248 @@
+// @ts-check
+import assert from 'node:assert/strict';
+import * as fs from 'node:fs';
+import { beforeEach, describe, it } from 'node:test';
+import { fileURLToPath } from 'node:url';
+import ts from 'typescript';
+import { loadFixture } from './test-utils.js';
+
+const createFixture = () => {
+ /** @type {Awaited<ReturnType<typeof loadFixture>>} */
+ let astroFixture;
+ /** @type {Record<string, string>} */
+ const writtenFiles = {};
+
+ /**
+ * @param {string} path
+ */
+ const getExpectedPath = (path) => fileURLToPath(new URL(path, astroFixture.config.root));
+
+ return {
+ /** @param {string} root */
+ async load(root) {
+ astroFixture = await loadFixture({ root });
+ return astroFixture.config;
+ },
+ clean() {
+ fs.rmSync(new URL('./.astro/', astroFixture.config.root), { force: true, recursive: true });
+ },
+ async whenSyncing() {
+ const fsMock = {
+ ...fs,
+ /**
+ * @param {fs.PathLike} path
+ * @param {string} contents
+ */
+ writeFileSync(path, contents) {
+ writtenFiles[path.toString()] = contents;
+ return fs.writeFileSync(path, contents);
+ },
+ promises: {
+ ...fs.promises,
+ /**
+ * @param {fs.PathLike} path
+ * @param {string} contents
+ */
+ writeFile(path, contents) {
+ writtenFiles[path.toString()] = contents;
+ return fs.promises.writeFile(path, contents);
+ },
+ },
+ };
+
+ await astroFixture.sync(
+ { root: fileURLToPath(astroFixture.config.root) },
+ {
+ // @ts-ignore
+ fs: fsMock,
+ },
+ );
+ },
+ /** @param {string} path */
+ thenFileShouldExist(path) {
+ assert.equal(
+ writtenFiles.hasOwnProperty(getExpectedPath(path)),
+ true,
+ `${path} does not exist`,
+ );
+ },
+ /**
+ * @param {string} path
+ * @param {string} content
+ * @param {string | undefined} error
+ */
+ thenFileContentShouldInclude(path, content, error = undefined) {
+ assert.equal(writtenFiles[getExpectedPath(path)].includes(content), true, error);
+ },
+ /**
+ * @param {string} path
+ * @param {string} content
+ * @param {string | undefined} error
+ */
+ thenFileContentShouldNotInclude(path, content, error = undefined) {
+ assert.equal(writtenFiles[getExpectedPath(path)].includes(content), false, error);
+ },
+ /**
+ * @param {string} path
+ */
+ thenFileShouldBeValidTypescript(path) {
+ try {
+ const content = writtenFiles[getExpectedPath(path)];
+ const result = ts.transpileModule(content, {
+ compilerOptions: {
+ module: ts.ModuleKind.ESNext,
+ },
+ });
+ assert.equal(
+ result.outputText,
+ '',
+ `${path} should be valid TypeScript. Output: ${result.outputText}`,
+ );
+ } catch (error) {
+ assert.fail(`${path} is not valid TypeScript. Error: ${error.message}`);
+ }
+ },
+ };
+};
+
+describe('astro sync', () => {
+ /** @type {ReturnType<typeof createFixture>} */
+ let fixture;
+ beforeEach(async () => {
+ fixture = createFixture();
+ });
+
+ it('Writes `.astro/types.d.ts`', async () => {
+ await fixture.load('./fixtures/astro-basic/');
+ fixture.clean();
+ await fixture.whenSyncing();
+ fixture.thenFileShouldExist('.astro/types.d.ts');
+ fixture.thenFileContentShouldInclude(
+ '.astro/types.d.ts',
+ `/// <reference types="astro/client" />`,
+ );
+ });
+
+ describe('Content collections', () => {
+ it('Adds reference to `.astro/types.d.ts`', async () => {
+ await fixture.load('./fixtures/content-collections/');
+ fixture.clean();
+ await fixture.whenSyncing();
+ fixture.thenFileShouldExist('.astro/types.d.ts');
+ fixture.thenFileContentShouldInclude(
+ '.astro/types.d.ts',
+ `/// <reference path="content.d.ts" />`,
+ );
+ fixture.thenFileShouldExist('.astro/content.d.ts');
+ fixture.thenFileContentShouldInclude(
+ '.astro/content.d.ts',
+ `declare module 'astro:content' {`,
+ 'Types file does not include `astro:content` module declaration',
+ );
+ fixture.thenFileShouldBeValidTypescript('.astro/content.d.ts');
+ });
+
+ it('Writes types for empty collections', async () => {
+ await fixture.load('./fixtures/content-collections-empty-dir/');
+ fixture.clean();
+ await fixture.whenSyncing();
+ fixture.thenFileContentShouldInclude(
+ '.astro/content.d.ts',
+ `"blog": Record<string, {
+ id: string;
+ render(): Render[".md"];
+ slug: string;
+ body: string;
+ collection: "blog";
+ data: InferEntrySchema<"blog">;
+ rendered?: RenderedContent;
+ filePath?: string;`,
+ 'Types file does not include empty collection type',
+ );
+ fixture.thenFileContentShouldInclude(
+ '.astro/content.d.ts',
+ `"blogMeta": Record<string, {
+ id: string;
+ body?: string;
+ collection: "blogMeta";
+ data: InferEntrySchema<"blogMeta">;
+ rendered?: RenderedContent;
+ filePath?: string;
+}>;`,
+ 'Types file does not include empty collection type',
+ );
+ });
+
+ it('does not write individual types for entries when emulating legacy collections', async () => {
+ await fixture.load('./fixtures/content-collections/');
+ fixture.clean();
+ await fixture.whenSyncing();
+ fixture.thenFileContentShouldNotInclude('.astro/content.d.ts', 'id: "one.md"');
+ });
+ });
+
+ describe('astro:env', () => {
+ it('Adds reference to `.astro/types.d.ts`', async () => {
+ await fixture.load('./fixtures/astro-env/');
+ fixture.clean();
+ await fixture.whenSyncing();
+ fixture.thenFileShouldExist('.astro/types.d.ts');
+ fixture.thenFileContentShouldInclude(
+ '.astro/types.d.ts',
+ `/// <reference path="env.d.ts" />`,
+ );
+ fixture.thenFileShouldExist('.astro/env.d.ts');
+ fixture.thenFileContentShouldInclude(
+ '.astro/env.d.ts',
+ `declare module 'astro:env/client' {`,
+ 'Types file does not include `astro:env` module declaration',
+ );
+ });
+
+ it('Does not throw if a public variable is required', async () => {
+ try {
+ await fixture.load('./fixtures/astro-env-required-public/');
+ fixture.clean();
+ await fixture.whenSyncing();
+ assert.ok(true);
+ } catch {
+ assert.fail();
+ }
+ });
+ it('Does not throw if a virtual module is imported in content.config.ts or import.meta.env is not loaded', async () => {
+ try {
+ await fixture.load('./fixtures/astro-env-content-collections/');
+ fixture.clean();
+ fs.writeFileSync(
+ new URL('./fixtures/astro-env-content-collections/.env', import.meta.url),
+ 'BAR=abc',
+ 'utf-8',
+ );
+ await fixture.whenSyncing();
+ assert.ok(true);
+ } catch {
+ assert.fail();
+ }
+ });
+ });
+
+ describe('astro:actions', () => {
+ it('Adds reference to `.astro/types.d.ts`', async () => {
+ await fixture.load('./fixtures/actions/');
+ fixture.clean();
+ await fixture.whenSyncing();
+ fixture.thenFileShouldExist('.astro/types.d.ts');
+ fixture.thenFileContentShouldInclude(
+ '.astro/types.d.ts',
+ `/// <reference path="actions.d.ts" />`,
+ );
+ fixture.thenFileShouldExist('.astro/actions.d.ts');
+ fixture.thenFileContentShouldInclude(
+ '.astro/actions.d.ts',
+ `declare module "astro:actions" {`,
+ 'Types file does not include `astro:actions` module declaration',
+ );
+ fixture.thenFileShouldBeValidTypescript('.astro/actions.d.ts');
+ });
+ });
+});