Skip to content

Commit

Permalink
Expose cosmos.fixtures.json with renderer URLs (#1629)
Browse files Browse the repository at this point in the history
  • Loading branch information
ovidiuch committed Mar 11, 2024
1 parent f83799a commit 4cc91ba
Show file tree
Hide file tree
Showing 12 changed files with 167 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
FixtureListItem,
} from '../../userModules/fixtureTypes.js';
import { addTreeNodeChild } from '../../utils/tree.js';
import { removeFixtureNameExtension } from '../fixtureUtils.js';
import { FixtureTreeNode } from '../types.js';

export function createRawFixtureTree(fixtures: FixtureList): FixtureTreeNode {
Expand Down Expand Up @@ -55,10 +56,6 @@ function parseFixturePath(fixturePath: string) {
};
}

function removeFixtureNameExtension(fixtureName: string) {
return fixtureName.replace(/\.(js|jsx|ts|tsx|md|mdx)$/, '');
}

function injectNode(
rootNode: FixtureTreeNode,
parents: string[],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { removeFixtureNameSuffix } from '../fixtureUtils.js';
import { FixtureTreeNode } from '../types.js';

export function hideFixtureSuffix(
Expand Down Expand Up @@ -25,10 +26,3 @@ export function hideFixtureSuffix(
}, {}),
};
}

function removeFixtureNameSuffix(
fixtureNameWithoutExtension: string,
suffix: string
) {
return fixtureNameWithoutExtension.replace(new RegExp(`\\.${suffix}$`), '');
}
10 changes: 10 additions & 0 deletions packages/react-cosmos-core/src/fixtureTree/fixtureUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export function removeFixtureNameExtension(fixtureName: string) {
return fixtureName.replace(/\.(js|jsx|ts|tsx|md|mdx)$/, '');
}

export function removeFixtureNameSuffix(
fixtureNameWithoutExtension: string,
suffix: string
) {
return fixtureNameWithoutExtension.replace(new RegExp(`\\.${suffix}$`), '');
}
1 change: 1 addition & 0 deletions packages/react-cosmos-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export * from './fixtureState/propsTypes.js';
export * from './fixtureState/types.js';
export * from './fixtureState/viewport.js';
export * from './fixtureTree/createFixtureTree/index.js';
export * from './fixtureTree/fixtureUtils.js';
export * from './fixtureTree/flattenFixtureTree.js';
export * from './fixtureTree/types.js';
export * from './message/serverMessage.js';
Expand Down
4 changes: 3 additions & 1 deletion packages/react-cosmos-core/src/renderer/rendererUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { CosmosCommand } from '../server/serverTypes.js';
import { FixtureId } from '../userModules/fixtureTypes.js';
import { buildRendererQueryString } from './rendererQueryString.js';

export type CosmosRendererUrl = null | string | { dev: string; export: string };

export function createRendererUrl(
rendererUrl: string,
fixtureId?: FixtureId,
Expand All @@ -24,7 +26,7 @@ export function createRendererUrl(
}

export function pickRendererUrl(
rendererUrl: undefined | null | string | { dev: string; export: string },
rendererUrl: undefined | CosmosRendererUrl,
command: CosmosCommand
): null | string {
return rendererUrl && typeof rendererUrl === 'object'
Expand Down
91 changes: 91 additions & 0 deletions packages/react-cosmos/src/corePlugins/fixturesJsonPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import express from 'express';
import fs from 'node:fs/promises';
import path from 'node:path';
import {
CosmosCommand,
createRendererUrl,
pickRendererUrl,
removeFixtureNameExtension,
removeFixtureNameSuffix,
} from 'react-cosmos-core';
import { CosmosConfig } from '../cosmosConfig/types.js';
import { CosmosServerPlugin } from '../cosmosPlugin/types.js';
import { findUserModulePaths } from '../userModules/findUserModulePaths.js';
import { importKeyPath } from '../userModules/shared.js';

export type CosmosFixtureJson = {
filePath: string;
cleanPath: string[];
rendererUrl: string;
};

export type CosmosFixturesJson = {
rendererUrl: string | null;
fixtures: CosmosFixtureJson[];
};

export const fixturesJsonPlugin: CosmosServerPlugin = {
name: 'fixturesJson',

devServer({ cosmosConfig, expressApp }) {
expressApp.get(
'/cosmos.fixtures.json',
(req: express.Request, res: express.Response) => {
res.json(createFixtureItems(cosmosConfig, 'dev'));
}
);
},

async export({ cosmosConfig }) {
const { exportPath } = cosmosConfig;
const json = createFixtureItems(cosmosConfig, 'export');
await fs.writeFile(
path.join(exportPath, 'cosmos.fixtures.json'),
JSON.stringify(json, null, 2)
);
},
};

function createFixtureItems(
cosmosConfig: CosmosConfig,
command: CosmosCommand
): CosmosFixturesJson {
const rendererUrl = pickRendererUrl(cosmosConfig.rendererUrl, command);
if (!rendererUrl) {
return {
rendererUrl: null,
fixtures: [],
};
}

const { fixturesDir, fixtureFileSuffix } = cosmosConfig;
const { fixturePaths } = findUserModulePaths(cosmosConfig);

return {
rendererUrl,
fixtures: fixturePaths.map(filePath => {
const relPath = importKeyPath(filePath, cosmosConfig.rootDir);
const fixtureId = { path: relPath };
return {
filePath: relPath,
cleanPath: cleanFixturePath(relPath, fixturesDir, fixtureFileSuffix),
rendererUrl: createRendererUrl(rendererUrl, fixtureId, true),
};
}),
};
}

function cleanFixturePath(
filePath: string,
fixturesDir: string,
fixtureSuffix: string
) {
const paths = filePath.split('/').filter(p => p !== fixturesDir);
return [
...paths.slice(0, -1),
removeFixtureNameSuffix(
removeFixtureNameExtension(paths[paths.length - 1]),
fixtureSuffix
),
];
}
2 changes: 2 additions & 0 deletions packages/react-cosmos/src/corePlugins/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { CosmosServerPlugin } from '../cosmosPlugin/types.js';
import { fixtureWatcherPlugin } from './fixtureWatcherPlugin.js';
import { fixturesJsonPlugin } from './fixturesJsonPlugin.js';
import { httpProxyPlugin } from './httpProxyPlugin.js';
import { openFilePlugin } from './openFilePlugin.js';
import { pluginEndpointPlugin } from './pluginEndpointPlugin.js';
import { portRetryPlugin } from './portRetryPlugin.js';

export const coreServerPlugins: CosmosServerPlugin[] = [
portRetryPlugin,
fixturesJsonPlugin,
httpProxyPlugin,
openFilePlugin,
pluginEndpointPlugin,
Expand Down
4 changes: 3 additions & 1 deletion packages/react-cosmos/src/cosmosConfig/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { CosmosRendererUrl } from 'react-cosmos-core';

interface HttpsOptions {
keyPath: string;
certPath: string;
Expand Down Expand Up @@ -34,7 +36,7 @@ export type CosmosConfig = {
portRetries: number;
plugins: string[];
publicUrl: string;
rendererUrl: null | string | { dev: string; export: string };
rendererUrl: CosmosRendererUrl;
rootDir: string;
staticPath: null | string;
watchDirs: string[];
Expand Down
8 changes: 2 additions & 6 deletions packages/react-cosmos/src/getFixtures/importUserModules.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import path from 'path';
import {
ByPath,
ReactDecoratorModule,
ReactFixtureModule,
} from 'react-cosmos-core';
import { CosmosConfig } from '../cosmosConfig/types.js';
import { findUserModulePaths } from '../userModules/findUserModulePaths.js';
import { slash } from '../utils/slash.js';
import { importKeyPath } from '../userModules/shared.js';

type UserModules = {
fixtures: ByPath<ReactFixtureModule>;
Expand All @@ -33,10 +32,7 @@ export function importUserModules({

function importModules<T>(paths: string[], rootDir: string) {
const modules = paths.map(p => {
// Converting to forward slashes on Windows is important because the
// slashes are used for generating a sorted list of fixtures and
// decorators.
const relPath = slash(path.relative(rootDir, p));
const relPath = importKeyPath(p, rootDir);
return { relPath, module: require(p) };
});

Expand Down
4 changes: 4 additions & 0 deletions packages/react-cosmos/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
export {
CosmosFixtureJson,
CosmosFixturesJson,
} from './corePlugins/fixturesJsonPlugin.js';
export * from './cosmosConfig/createCosmosConfig.js';
export * from './cosmosConfig/detectCosmosConfig.js';
export * from './cosmosConfig/getCosmosConfigAtPath.js';
Expand Down
3 changes: 3 additions & 0 deletions packages/react-cosmos/src/userModules/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ export function createImportMap(
}

export function importKeyPath(filePath: string, rootDir: string) {
// Converting to forward slashes on Windows is important because the
// slashes are used for generating a sorted list of fixtures and
// decorators.
return slash(path.relative(rootDir, filePath));
}

Expand Down
47 changes: 46 additions & 1 deletion tests/helpers/webTests.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Page, expect, test } from '@playwright/test';
import { APIRequestContext, Page, expect, test } from '@playwright/test';
import { CosmosFixtureJson, CosmosFixturesJson } from 'react-cosmos';
import { exampleName } from './envVars.js';

export function webTests(url: string) {
Expand Down Expand Up @@ -85,8 +86,52 @@ export function webTests(url: string) {
expect(await response.text()).toContain('nom nom nom');
});
});

test.describe('cosmos.fixture.json', () => {
test('contains renderer URL', async ({ request }) => {
const { rendererUrl } = await getFixturesJson(request, url);
expect(typeof rendererUrl).toBe('string');
});

test('contains fixture data', async ({ request }) => {
const { fixtures } = await getFixturesJson(request, url);
expect(fixtures).toContainEqual({
filePath: 'src/WelcomeMessage/WelcomeMessage.fixture.tsx',
cleanPath: ['src', 'WelcomeMessage', 'WelcomeMessage'],
rendererUrl: expect.stringContaining(
'?fixtureId=%7B%22path%22%3A%22src%2FWelcomeMessage%2FWelcomeMessage.fixture.tsx%22%7D&locked=true'
),
});
});

test('contains fixture renderer URL', async ({ request, page }) => {
const { fixtures } = await getFixturesJson(request, url);
const fixture = expectFixture(fixtures, 'HelloWorld.mdx');
await page.goto(resolveRendererUrl(url, fixture.rendererUrl));
await expect(page.getByText('Hello World!')).toBeVisible();
});
});
}

function rendererRoot(page: Page) {
return page.frameLocator('iframe').locator('#root');
}

async function getFixturesJson(request: APIRequestContext, url: string) {
const response = await request.get(url + '/cosmos.fixtures.json');
return (await response.json()) as CosmosFixturesJson;
}

function expectFixture(fixtures: CosmosFixtureJson[], fileName: string) {
const fixture = fixtures.find(f => f.filePath.endsWith(fileName));
expect(fixture).toBeTruthy();
return fixture!;
}

function resolveRendererUrl(url: string, rendererUrl: string) {
try {
return new URL(rendererUrl).href;
} catch (err) {
return new URL(rendererUrl, url).href;
}
}

0 comments on commit 4cc91ba

Please sign in to comment.