fix: 0-byte index.html day archives
This commit is contained in:
@@ -18,6 +18,7 @@ interface RenderContext {
|
|||||||
menu?: MenuDocument;
|
menu?: MenuDocument;
|
||||||
skipContextSetup?: boolean;
|
skipContextSetup?: boolean;
|
||||||
maxPostsPerPage?: number;
|
maxPostsPerPage?: number;
|
||||||
|
allowEmptyArchiveRender?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createGenerationRouteRenderer(params: {
|
export function createGenerationRouteRenderer(params: {
|
||||||
@@ -111,6 +112,10 @@ export function createPreviewBackedGenerationRouteRenderer(params: {
|
|||||||
|
|
||||||
const serializeFilter = (filter: unknown): string => {
|
const serializeFilter = (filter: unknown): string => {
|
||||||
const normalizeValue = (value: unknown): unknown => {
|
const normalizeValue = (value: unknown): unknown => {
|
||||||
|
if (value instanceof Date) {
|
||||||
|
return value.toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
return value.map((entry) => normalizeValue(entry));
|
return value.map((entry) => normalizeValue(entry));
|
||||||
}
|
}
|
||||||
@@ -244,6 +249,7 @@ export function createPreviewBackedGenerationRouteRenderer(params: {
|
|||||||
menu,
|
menu,
|
||||||
skipContextSetup: true,
|
skipContextSetup: true,
|
||||||
maxPostsPerPage: params.maxPostsPerPage,
|
maxPostsPerPage: params.maxPostsPerPage,
|
||||||
|
allowEmptyArchiveRender: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1105,10 +1105,11 @@ export class PageRenderer {
|
|||||||
html_theme_attribute?: string;
|
html_theme_attribute?: string;
|
||||||
pagination?: PaginationContext;
|
pagination?: PaginationContext;
|
||||||
categorySettings?: Record<string, CategoryRenderSettings>;
|
categorySettings?: Record<string, CategoryRenderSettings>;
|
||||||
|
renderEmptyState?: boolean;
|
||||||
},
|
},
|
||||||
postEngine?: PostEngineContract,
|
postEngine?: PostEngineContract,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
if (posts.length === 0) {
|
if (posts.length === 0 && !options.renderEmptyState) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -179,6 +179,7 @@ export class PreviewServer {
|
|||||||
maxPostsPerPage?: number;
|
maxPostsPerPage?: number;
|
||||||
requestTheme?: string | null;
|
requestTheme?: string | null;
|
||||||
htmlThemeAttribute?: string;
|
htmlThemeAttribute?: string;
|
||||||
|
allowEmptyArchiveRender?: boolean;
|
||||||
singlePostOptions?: { useDraftContent?: boolean; draftPostId?: string };
|
singlePostOptions?: { useDraftContent?: boolean; draftPostId?: string };
|
||||||
},
|
},
|
||||||
): Promise<string | null> {
|
): Promise<string | null> {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export interface SharedRouteRenderOptions {
|
|||||||
maxPostsPerPage?: number;
|
maxPostsPerPage?: number;
|
||||||
requestTheme?: string | null;
|
requestTheme?: string | null;
|
||||||
htmlThemeAttribute?: string;
|
htmlThemeAttribute?: string;
|
||||||
|
allowEmptyArchiveRender?: boolean;
|
||||||
singlePostOptions?: { useDraftContent?: boolean; draftPostId?: string };
|
singlePostOptions?: { useDraftContent?: boolean; draftPostId?: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,6 +98,7 @@ async function resolveRouteWithSharedServices(
|
|||||||
tagColorByName: Record<string, string>,
|
tagColorByName: Record<string, string>,
|
||||||
listExcludedCategories: string[],
|
listExcludedCategories: string[],
|
||||||
services: SharedRouteRenderServices<CategoryMetadata>,
|
services: SharedRouteRenderServices<CategoryMetadata>,
|
||||||
|
allowEmptyArchiveRender: boolean,
|
||||||
singlePostOptions?: { useDraftContent?: boolean; draftPostId?: string },
|
singlePostOptions?: { useDraftContent?: boolean; draftPostId?: string },
|
||||||
): Promise<string | null> {
|
): Promise<string | null> {
|
||||||
const routePagination = parseRoutePagination(pathname);
|
const routePagination = parseRoutePagination(pathname);
|
||||||
@@ -125,6 +127,7 @@ async function resolveRouteWithSharedServices(
|
|||||||
menu_items: pageContext.menuItems,
|
menu_items: pageContext.menuItems,
|
||||||
pico_stylesheet_href: pageContext.picoStylesheetHref,
|
pico_stylesheet_href: pageContext.picoStylesheetHref,
|
||||||
html_theme_attribute: pageContext.htmlThemeAttribute,
|
html_theme_attribute: pageContext.htmlThemeAttribute,
|
||||||
|
renderEmptyState: allowEmptyArchiveRender,
|
||||||
}, services.postEngineForMacros);
|
}, services.postEngineForMacros);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,6 +147,7 @@ async function resolveRouteWithSharedServices(
|
|||||||
menu_items: pageContext.menuItems,
|
menu_items: pageContext.menuItems,
|
||||||
pico_stylesheet_href: pageContext.picoStylesheetHref,
|
pico_stylesheet_href: pageContext.picoStylesheetHref,
|
||||||
html_theme_attribute: pageContext.htmlThemeAttribute,
|
html_theme_attribute: pageContext.htmlThemeAttribute,
|
||||||
|
renderEmptyState: allowEmptyArchiveRender,
|
||||||
}, services.postEngineForMacros);
|
}, services.postEngineForMacros);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,6 +168,7 @@ async function resolveRouteWithSharedServices(
|
|||||||
menu_items: pageContext.menuItems,
|
menu_items: pageContext.menuItems,
|
||||||
pico_stylesheet_href: pageContext.picoStylesheetHref,
|
pico_stylesheet_href: pageContext.picoStylesheetHref,
|
||||||
html_theme_attribute: pageContext.htmlThemeAttribute,
|
html_theme_attribute: pageContext.htmlThemeAttribute,
|
||||||
|
renderEmptyState: allowEmptyArchiveRender,
|
||||||
}, services.postEngineForMacros);
|
}, services.postEngineForMacros);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,6 +211,7 @@ async function resolveRouteWithSharedServices(
|
|||||||
menu_items: pageContext.menuItems,
|
menu_items: pageContext.menuItems,
|
||||||
pico_stylesheet_href: pageContext.picoStylesheetHref,
|
pico_stylesheet_href: pageContext.picoStylesheetHref,
|
||||||
html_theme_attribute: pageContext.htmlThemeAttribute,
|
html_theme_attribute: pageContext.htmlThemeAttribute,
|
||||||
|
renderEmptyState: allowEmptyArchiveRender,
|
||||||
}, services.postEngineForMacros);
|
}, services.postEngineForMacros);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,6 +233,7 @@ async function resolveRouteWithSharedServices(
|
|||||||
menu_items: pageContext.menuItems,
|
menu_items: pageContext.menuItems,
|
||||||
pico_stylesheet_href: pageContext.picoStylesheetHref,
|
pico_stylesheet_href: pageContext.picoStylesheetHref,
|
||||||
html_theme_attribute: pageContext.htmlThemeAttribute,
|
html_theme_attribute: pageContext.htmlThemeAttribute,
|
||||||
|
renderEmptyState: allowEmptyArchiveRender,
|
||||||
}, services.postEngineForMacros);
|
}, services.postEngineForMacros);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,6 +253,7 @@ async function resolveRouteWithSharedServices(
|
|||||||
menu_items: pageContext.menuItems,
|
menu_items: pageContext.menuItems,
|
||||||
pico_stylesheet_href: pageContext.picoStylesheetHref,
|
pico_stylesheet_href: pageContext.picoStylesheetHref,
|
||||||
html_theme_attribute: pageContext.htmlThemeAttribute,
|
html_theme_attribute: pageContext.htmlThemeAttribute,
|
||||||
|
renderEmptyState: allowEmptyArchiveRender,
|
||||||
}, services.postEngineForMacros);
|
}, services.postEngineForMacros);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,5 +318,5 @@ export async function renderRouteWithSharedContext<TCategoryMetadata>(
|
|||||||
menuItems,
|
menuItems,
|
||||||
picoStylesheetHref,
|
picoStylesheetHref,
|
||||||
htmlThemeAttribute: options.htmlThemeAttribute,
|
htmlThemeAttribute: options.htmlThemeAttribute,
|
||||||
}, categorySettings, categoryMetadata as Record<string, CategoryMetadata>, tagColorByName, listExcludedCategories, services as SharedRouteRenderServices<CategoryMetadata>, options.singlePostOptions);
|
}, categorySettings, categoryMetadata as Record<string, CategoryMetadata>, tagColorByName, listExcludedCategories, services as SharedRouteRenderServices<CategoryMetadata>, options.allowEmptyArchiveRender === true, options.singlePostOptions);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,8 +55,12 @@ function extractSitemapLocs(sitemapXml: string): string[] {
|
|||||||
return locs;
|
return locs;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function collectHtmlIndexPaths(htmlDir: string): Promise<Set<string>> {
|
async function collectHtmlIndexPaths(htmlDir: string): Promise<{
|
||||||
|
existingHtmlPathSet: Set<string>;
|
||||||
|
zeroByteHtmlPathSet: Set<string>;
|
||||||
|
}> {
|
||||||
const existingHtmlPathSet = new Set<string>();
|
const existingHtmlPathSet = new Set<string>();
|
||||||
|
const zeroByteHtmlPathSet = new Set<string>();
|
||||||
|
|
||||||
const collectIndexPaths = async (dir: string, relativePrefix = ''): Promise<void> => {
|
const collectIndexPaths = async (dir: string, relativePrefix = ''): Promise<void> => {
|
||||||
let entries: Array<{ name: string; isDirectory: () => boolean; isFile: () => boolean }>;
|
let entries: Array<{ name: string; isDirectory: () => boolean; isFile: () => boolean }>;
|
||||||
@@ -80,12 +84,28 @@ async function collectHtmlIndexPaths(htmlDir: string): Promise<Set<string>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const normalizedRelative = nextRelative.replace(/(^|\/)index\.html$/, '');
|
const normalizedRelative = nextRelative.replace(/(^|\/)index\.html$/, '');
|
||||||
existingHtmlPathSet.add(normalizeUrlPath(normalizedRelative ? `/${normalizedRelative}` : '/'));
|
const normalizedUrlPath = normalizeUrlPath(normalizedRelative ? `/${normalizedRelative}` : '/');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const stats = await fs.stat(nextPath);
|
||||||
|
if (stats.size <= 0) {
|
||||||
|
zeroByteHtmlPathSet.add(normalizedUrlPath);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
zeroByteHtmlPathSet.add(normalizedUrlPath);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
existingHtmlPathSet.add(normalizedUrlPath);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
await collectIndexPaths(htmlDir);
|
await collectIndexPaths(htmlDir);
|
||||||
return existingHtmlPathSet;
|
return {
|
||||||
|
existingHtmlPathSet,
|
||||||
|
zeroByteHtmlPathSet,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function compareSitemapToHtml(params: CompareSitemapToHtmlParams): Promise<SiteValidationDiffResult> {
|
export async function compareSitemapToHtml(params: CompareSitemapToHtmlParams): Promise<SiteValidationDiffResult> {
|
||||||
@@ -95,7 +115,7 @@ export async function compareSitemapToHtml(params: CompareSitemapToHtmlParams):
|
|||||||
.map((value) => normalizeUrlPath(value)),
|
.map((value) => normalizeUrlPath(value)),
|
||||||
);
|
);
|
||||||
|
|
||||||
const existingHtmlPathSet = await collectHtmlIndexPaths(params.htmlDir);
|
const { existingHtmlPathSet, zeroByteHtmlPathSet } = await collectHtmlIndexPaths(params.htmlDir);
|
||||||
|
|
||||||
const missingUrlPaths = Array.from(expectedPathSet)
|
const missingUrlPaths = Array.from(expectedPathSet)
|
||||||
.filter((value) => !existingHtmlPathSet.has(value))
|
.filter((value) => !existingHtmlPathSet.has(value))
|
||||||
@@ -103,6 +123,8 @@ export async function compareSitemapToHtml(params: CompareSitemapToHtmlParams):
|
|||||||
|
|
||||||
const extraUrlPaths = Array.from(existingHtmlPathSet)
|
const extraUrlPaths = Array.from(existingHtmlPathSet)
|
||||||
.filter((value) => !expectedPathSet.has(value))
|
.filter((value) => !expectedPathSet.has(value))
|
||||||
|
.concat(Array.from(zeroByteHtmlPathSet).filter((value) => !expectedPathSet.has(value)))
|
||||||
|
.filter((value, index, array) => array.indexOf(value) === index)
|
||||||
.sort();
|
.sort();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,5 +1,28 @@
|
|||||||
import { describe, expect, it, vi } from 'vitest';
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
import { createGenerationRouteRenderer } from '../../src/main/engine/GenerationRouteRendererFactory';
|
import type { PostData } from '../../src/main/engine/PostEngine';
|
||||||
|
import {
|
||||||
|
createGenerationRouteRenderer,
|
||||||
|
createPreviewBackedGenerationRouteRenderer,
|
||||||
|
} from '../../src/main/engine/GenerationRouteRendererFactory';
|
||||||
|
|
||||||
|
function makePost(overrides: Partial<PostData> = {}): PostData {
|
||||||
|
const createdAt = overrides.createdAt ?? new Date('2025-01-15T10:00:00.000Z');
|
||||||
|
return {
|
||||||
|
id: overrides.id ?? 'post-1',
|
||||||
|
projectId: overrides.projectId ?? 'project',
|
||||||
|
title: overrides.title ?? 'Title',
|
||||||
|
slug: overrides.slug ?? 'title',
|
||||||
|
excerpt: overrides.excerpt,
|
||||||
|
content: overrides.content ?? 'Body',
|
||||||
|
status: overrides.status ?? 'published',
|
||||||
|
author: overrides.author,
|
||||||
|
createdAt,
|
||||||
|
updatedAt: overrides.updatedAt ?? createdAt,
|
||||||
|
publishedAt: overrides.publishedAt ?? createdAt,
|
||||||
|
tags: overrides.tags ?? [],
|
||||||
|
categories: overrides.categories ?? [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
describe('GenerationRouteRendererFactory', () => {
|
describe('GenerationRouteRendererFactory', () => {
|
||||||
it('normalizes route keys and memoizes html rendering calls', async () => {
|
it('normalizes route keys and memoizes html rendering calls', async () => {
|
||||||
@@ -31,4 +54,86 @@ describe('GenerationRouteRendererFactory', () => {
|
|||||||
expect(renderWithContext).toHaveBeenCalledTimes(1);
|
expect(renderWithContext).toHaveBeenCalledTimes(1);
|
||||||
expect(renderWithContext).toHaveBeenCalledWith('/foo', expect.any(Object));
|
expect(renderWithContext).toHaveBeenCalledWith('/foo', expect.any(Object));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('keeps day archive query caches distinct across different dates', async () => {
|
||||||
|
const posts = [
|
||||||
|
makePost({
|
||||||
|
id: 'day-1',
|
||||||
|
slug: 'day-1',
|
||||||
|
title: 'Post On Day One',
|
||||||
|
createdAt: new Date('2025-01-15T10:00:00.000Z'),
|
||||||
|
}),
|
||||||
|
makePost({
|
||||||
|
id: 'day-2',
|
||||||
|
slug: 'day-2',
|
||||||
|
title: 'Post On Day Two',
|
||||||
|
createdAt: new Date('2025-01-16T10:00:00.000Z'),
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
const postEngine = {
|
||||||
|
getPostsFiltered: vi.fn(async (filter: {
|
||||||
|
status?: 'draft' | 'published' | 'archived';
|
||||||
|
startDate?: Date;
|
||||||
|
endDate?: Date;
|
||||||
|
year?: number;
|
||||||
|
month?: number;
|
||||||
|
}) => {
|
||||||
|
let filtered = posts.filter((post) => post.status === (filter.status ?? post.status));
|
||||||
|
|
||||||
|
if (typeof filter.year === 'number') {
|
||||||
|
filtered = filtered.filter((post) => post.createdAt.getFullYear() === filter.year);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof filter.month === 'number') {
|
||||||
|
filtered = filtered.filter((post) => post.createdAt.getMonth() === filter.month);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter.startDate) {
|
||||||
|
filtered = filtered.filter((post) => post.createdAt >= filter.startDate as Date);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter.endDate) {
|
||||||
|
filtered = filtered.filter((post) => post.createdAt <= filter.endDate as Date);
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered;
|
||||||
|
}),
|
||||||
|
getPublishedVersion: vi.fn(async () => null),
|
||||||
|
findPublishedBySlug: vi.fn(async (slug: string) => posts.find((post) => post.slug === slug) ?? null),
|
||||||
|
getPost: vi.fn(async (id: string) => posts.find((post) => post.id === id) ?? null),
|
||||||
|
hasPublishedVersion: vi.fn(async () => false),
|
||||||
|
setProjectContext: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderRoute = createPreviewBackedGenerationRouteRenderer({
|
||||||
|
options: {
|
||||||
|
projectId: 'project',
|
||||||
|
dataDir: '/tmp',
|
||||||
|
projectName: 'Project',
|
||||||
|
},
|
||||||
|
maxPostsPerPage: 50,
|
||||||
|
publishedPostsForLookup: posts,
|
||||||
|
engines: {
|
||||||
|
postEngine,
|
||||||
|
mediaEngine: {
|
||||||
|
getAllMedia: vi.fn(async () => []),
|
||||||
|
setProjectContext: vi.fn(),
|
||||||
|
},
|
||||||
|
postMediaEngine: {
|
||||||
|
setProjectContext: vi.fn(),
|
||||||
|
getLinkedMediaForPost: vi.fn(async () => []),
|
||||||
|
getLinkedMediaDataForPost: vi.fn(async () => []),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const dayOneHtml = await renderRoute('/2025/01/15');
|
||||||
|
const dayTwoHtml = await renderRoute('/2025/01/16');
|
||||||
|
|
||||||
|
expect(dayOneHtml).toContain('Post On Day One');
|
||||||
|
expect(dayOneHtml).not.toContain('Post On Day Two');
|
||||||
|
expect(dayTwoHtml).toContain('Post On Day Two');
|
||||||
|
expect(dayTwoHtml).not.toContain('Post On Day One');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
34
tests/engine/PageRenderer.renderPostList.test.ts
Normal file
34
tests/engine/PageRenderer.renderPostList.test.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { PageRenderer, type HtmlRewriteContext } from '../../src/main/engine/PageRenderer';
|
||||||
|
|
||||||
|
const rewriteContext: HtmlRewriteContext = {
|
||||||
|
canonicalPostPathBySlug: new Map<string, string>(),
|
||||||
|
canonicalMediaPathBySourcePath: new Map<string, string>(),
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('PageRenderer.renderPostList', () => {
|
||||||
|
it('renders base framework for empty day archive pages instead of returning empty html', async () => {
|
||||||
|
const renderer = new PageRenderer(
|
||||||
|
{ getAllMedia: async () => [] },
|
||||||
|
{
|
||||||
|
getLinkedMediaDataForPost: async () => [],
|
||||||
|
setProjectContext: () => {},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const html = await renderer.renderPostList([], rewriteContext, {
|
||||||
|
archiveGrouping: true,
|
||||||
|
routeKind: 'date',
|
||||||
|
archiveContext: { kind: 'day', year: 2026, month: 2, day: 22 },
|
||||||
|
basePathname: '/2026/02/22',
|
||||||
|
page_title: 'Test Blog',
|
||||||
|
language: 'en',
|
||||||
|
menu_items: [],
|
||||||
|
renderEmptyState: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(html).toContain('<!doctype html>');
|
||||||
|
expect(html).toContain('<section class="post-list"');
|
||||||
|
expect(html).toContain('<h1 class="archive-heading">Archive 22. February 2026</h1>');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -63,4 +63,31 @@ describe('SiteValidationDiffService', () => {
|
|||||||
expect(result.expectedUrlCount).toBe(2);
|
expect(result.expectedUrlCount).toBe(2);
|
||||||
expect(result.existingHtmlUrlCount).toBe(0);
|
expect(result.existingHtmlUrlCount).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('treats zero-byte index pages as missing routes that need regeneration', async () => {
|
||||||
|
const tempRoot = path.join('/tmp', makeTempName());
|
||||||
|
const htmlDir = path.join(tempRoot, 'html');
|
||||||
|
|
||||||
|
await mkdir(path.join(htmlDir, '2026', '02', '22'), { recursive: true });
|
||||||
|
await writeFile(path.join(htmlDir, '2026', '02', '22', 'index.html'), '', 'utf-8');
|
||||||
|
|
||||||
|
const sitemapXml = [
|
||||||
|
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||||
|
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
|
||||||
|
' <url><loc>https://example.com/2026/02/22/</loc></url>',
|
||||||
|
'</urlset>',
|
||||||
|
'',
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const result = await compareSitemapToHtml({
|
||||||
|
sitemapXml,
|
||||||
|
baseUrl: 'https://example.com',
|
||||||
|
htmlDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.missingUrlPaths).toEqual(['/2026/02/22']);
|
||||||
|
expect(result.extraUrlPaths).toEqual([]);
|
||||||
|
expect(result.expectedUrlCount).toBe(1);
|
||||||
|
expect(result.existingHtmlUrlCount).toBe(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user