Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions apps/blog-app-e2e/tests/app.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ test('should redirect to /blog', async ({ page }) => {
await expect(page).toHaveURL(/\/blog$/);
});

// https://github.com/analogjs/analog/issues/2165
test.fixme('should serve up HTML for pre-rendered markdown route', async ({
test('should serve up HTML for pre-rendered markdown route', async ({
page,
}) => {
await page.goto('/blog/2022-12-27-my-first-post');
Expand Down
139 changes: 113 additions & 26 deletions packages/content/resources/src/content-file-resource.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
import { Injectable, InjectionToken, Signal, signal } from '@angular/core';
import {
Injectable,
InjectionToken,
Signal,
signal,
type Provider,
} from '@angular/core';
import { ApplicationRef } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { ActivatedRoute, convertToParamMap } from '@angular/router';
import type { StandardSchemaV1 } from '@standard-schema/spec';
import { of } from 'rxjs';
import { describe, expect, it } from 'vitest';

import { CONTENT_FILES_TOKEN } from '../../src/lib/content-files-token';
import {
CONTENT_FILE_LOADER,
withContentFileLoader,
} from '../../src/lib/content-file-loader';
import { contentFileResource } from './content-file-resource';
import { CONTENT_FILE_LOADER } from '../../src/lib/content-file-loader';
import { ContentRenderer } from '../../src/lib/content-renderer';

const TEST_RESOURCE_TOKEN = new InjectionToken<
Expand Down Expand Up @@ -218,6 +228,61 @@ title: Hello World
});
});

it('uses the injected content file loader when provided', async () => {
const contentFiles = {
'/src/content/async/loader.md': () =>
Promise.resolve(`---
slug: 'async/loader'
---
# Loaded Async`),
};

setup({
routeParams: { slug: 'async/loader' },
contentFiles: {},
contentFileLoader: async () => contentFiles,
provideContentFilesToken: false,
});

const result = TestBed.inject(TEST_RESOURCE_TOKEN);
await settleResource(result);

expect(result.value()).toEqual({
filename: '/src/content/async/loader',
slug: 'async/loader',
attributes: { slug: 'async/loader' },
content: '# Loaded Async',
toc: [{ id: 'loaded-async', level: 1, text: 'Loaded Async' }],
});
});

it('supports the default content file loader provider', async () => {
const contentFiles = {
'/src/content/default-loader.md': () =>
Promise.resolve(`---
slug: 'default-loader'
---
# Default Loader`),
};

setup({
routeParams: { slug: 'default-loader' },
contentFiles,
providers: [withContentFileLoader()],
});

const result = TestBed.inject(TEST_RESOURCE_TOKEN);
await settleResource(result);

expect(result.value()).toEqual({
filename: '/src/content/default-loader',
slug: 'default-loader',
attributes: { slug: 'default-loader' },
content: '# Default Loader',
toc: [{ id: 'default-loader', level: 1, text: 'Default Loader' }],
});
});

it('validates module metadata when a schema is provided', async () => {
const contentFiles = {
'/src/content/guides/intro.md': () =>
Expand Down Expand Up @@ -298,34 +363,56 @@ function setup(args: {
string,
() => Promise<string | { default: any; metadata: any }>
>;
contentFileLoader?: () => Promise<
Record<string, () => Promise<string | { default: any; metadata: any }>>
>;
params?: Signal<string | { customFilename: string }>;
provideContentFilesToken?: boolean;
providers?: Provider[];
schema?: StandardSchemaV1;
}) {
TestBed.configureTestingModule({
providers: [
{
provide: ActivatedRoute,
useValue: {
paramMap: of(convertToParamMap(args.routeParams)),
},
const providers = [
{
provide: ActivatedRoute,
useValue: {
paramMap: of(convertToParamMap(args.routeParams)),
},
{
provide: ContentRenderer,
useClass: TestContentRenderer,
},
{
provide: CONTENT_FILE_LOADER,
useValue: async () =>
args.contentFiles as Record<string, () => Promise<string>>,
},
{
provide: TEST_RESOURCE_TOKEN,
useFactory: () =>
args.schema
? contentFileResource({ params: args.params, schema: args.schema })
: contentFileResource(args.params),
},
],
},
{
provide: ContentRenderer,
useClass: TestContentRenderer,
},
...(args.provideContentFilesToken === false
? []
: [
{
provide: CONTENT_FILES_TOKEN,
useValue: args.contentFiles as Record<
string,
() => Promise<string>
>,
},
]),
...(args.contentFileLoader
? [
{
provide: CONTENT_FILE_LOADER,
useValue: args.contentFileLoader,
},
]
: []),
...(args.providers ?? []),
{
provide: TEST_RESOURCE_TOKEN,
useFactory: () =>
args.schema
? contentFileResource({ params: args.params, schema: args.schema })
: contentFileResource(args.params),
},
];

TestBed.configureTestingModule({
providers,
});
}

Expand Down
17 changes: 11 additions & 6 deletions packages/content/resources/src/content-file-resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,20 @@ import {
import { ActivatedRoute } from '@angular/router';

import { toSignal } from '@angular/core/rxjs-interop';
import { from } from 'rxjs';
import { map } from 'rxjs/operators';
import type { ContentFile } from '../../src/lib/content-file';
import {
CONTENT_FILE_LOADER,
injectContentFileLoader,
} from '../../src/lib/content-file-loader';
import { injectContentLocale } from '../../src/lib/content-locale';
import { ContentRenderer } from '../../src/lib/content-renderer';
import { injectContentFilesMap } from '../../src/lib/inject-content-files';
Comment thread
coderabbitai[bot] marked this conversation as resolved.
import {
FrontmatterValidationError,
parseRawContentFile,
parseRawContentFileAsync,
} from '../../src/lib/parse-raw-content-file';
import { injectContentFileLoader } from '../../src/lib/content-file-loader';

export interface ContentFileResourceResult<
Attributes extends Record<string, any> = Record<string, any>,
Expand Down Expand Up @@ -209,10 +212,11 @@ export function contentFileResource(
? (paramsOrOptions as { schema?: StandardSchemaV1 }).schema
: undefined;

const loaderPromise = injectContentFileLoader();
const contentRenderer = inject(ContentRenderer);
const locale = injectContentLocale();
const contentFilesMap = toSignal(from(loaderPromise()));
const contentFilesMap = inject(CONTENT_FILE_LOADER, { optional: true })
? injectContentFileLoader()()
: Promise.resolve(injectContentFilesMap());
const input =
params ||
toSignal(
Expand All @@ -223,9 +227,10 @@ export function contentFileResource(
);

return resource({
params: computed(() => ({ input: input(), files: contentFilesMap() })),
params: computed(() => input()),
loader: async ({ params: resourceParams }) => {
const { input: param, files } = resourceParams;
const param = resourceParams;
const files = await contentFilesMap;

if (typeof param === 'string') {
if (param) {
Expand Down
8 changes: 4 additions & 4 deletions packages/content/src/lib/get-content-files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
*
* @returns
*/
export const getContentFilesList = () => {
const ANALOG_CONTENT_FILE_LIST = {};
export const ANALOG_CONTENT_FILE_LIST = {};

export const getContentFilesList = () => {
return ANALOG_CONTENT_FILE_LIST as Record<string, Record<string, any>>;
};

Expand All @@ -16,8 +16,8 @@ export const getContentFilesList = () => {
*
* @returns
*/
export const getContentFiles = (): Record<string, () => Promise<string>> => {
const ANALOG_CONTENT_ROUTE_FILES = {};
export const ANALOG_CONTENT_ROUTE_FILES = {};

export const getContentFiles = (): Record<string, () => Promise<string>> => {
return ANALOG_CONTENT_ROUTE_FILES as Record<string, () => Promise<string>>;
};
14 changes: 14 additions & 0 deletions packages/platform/src/lib/content-plugin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,20 @@ describe('content plugin', () => {
);
});

it('transforms exported content list placeholders from built workspace packages', () => {
vi.mocked(globSync).mockReturnValueOnce([
`${appRoot}/src/content/post.md`,
]);

const { transform } = getDiscoveryPlugins();
const result = transform.handler(
'export const ANALOG_CONTENT_FILE_LIST = {};',
);

expect(extractKeys(result.code)).toEqual(['/src/content/post.md']);
expect(result.code).not.toContain('ANALOG_CONTENT_FILE_LIST = {};');
});

it('normalizes workspace content keys outside app root', () => {
vi.mocked(globSync).mockReturnValueOnce([
'libs/shared/feature/src/content/post.md',
Expand Down
4 changes: 2 additions & 2 deletions packages/platform/src/lib/content-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,9 @@ export function contentPlugin(
});

let result = code.replace(
'const ANALOG_CONTENT_FILE_LIST = {};',
'ANALOG_CONTENT_FILE_LIST = {};',
`
let ANALOG_CONTENT_FILE_LIST = {${contentFilesList.map(
ANALOG_CONTENT_FILE_LIST = {${contentFilesList.map(
(module, index) =>
`"${getContentModuleKey(module)}": analog_module_${index}`,
)}};
Expand Down
Loading
Loading