generated from polymech/site-template
tests:filter/model - basics
This commit is contained in:
parent
060ef763d3
commit
966a0ede75
@ -23,7 +23,7 @@
|
||||
"format": "unix-time"
|
||||
}
|
||||
],
|
||||
"default": "2025-03-28T07:16:15.601Z"
|
||||
"default": "2025-03-28T07:40:51.377Z"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
|
||||
export default new Map([
|
||||
["src/content/infopages/contact.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Finfopages%2Fcontact.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/resources/workflow.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fresources%2Fworkflow.mdx&astroContentModuleFlag=true")]]);
|
||||
["src/content/resources/workflow.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fresources%2Fworkflow.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/infopages/contact.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Finfopages%2Fcontact.mdx&astroContentModuleFlag=true")]]);
|
||||
|
||||
File diff suppressed because one or more lines are too long
3703
package-lock.json
generated
3703
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
31
package.json
31
package.json
@ -11,13 +11,16 @@
|
||||
"preview": "astro preview",
|
||||
"astro": "astro",
|
||||
"generate-pwa-assets": "pwa-assets-generator --preset minimal-2023 public/logo.svg",
|
||||
"test": "playwright test",
|
||||
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
|
||||
"test:lighthouse": "lighthouse https://polymech.io/en --output json --output html --output-path ./dist/reports/report.html --save-assets --chrome-flags=\"--window-size=1440,700 --headless\"",
|
||||
"test:debug": "playwright test",
|
||||
"test:ui": "playwright test --ui",
|
||||
"clean": "rm -rf dist",
|
||||
"test:base": "vitest run src/base",
|
||||
"test:base:watch": "vitest watch src/base"
|
||||
"test:base:watch": "vitest watch src/base",
|
||||
"test:model": "vitest run src/model",
|
||||
"test:model:watch": "vitest watch src/model",
|
||||
"test:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/compiler": "^2.10.4",
|
||||
@ -93,10 +96,30 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/google-publisher-tag": "^1.20250210.0",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@vitest/coverage-v8": "^1.3.1",
|
||||
"jest": "^29.7.0",
|
||||
"micromark-util-sanitize-uri": "^2.0.1",
|
||||
"normalize-url": "^8.0.1",
|
||||
"sass-embedded": "^1.83.4",
|
||||
"vitest": "^1.3.1",
|
||||
"@vitest/coverage-v8": "^1.3.1"
|
||||
"ts-jest": "^29.3.0",
|
||||
"vitest": "^1.3.1"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "ts-jest/presets/default-esm",
|
||||
"testEnvironment": "node",
|
||||
"extensionsToTreatAsEsm": [".ts", ".tsx"],
|
||||
"moduleNameMapper": {
|
||||
"^@/(.*)$": "<rootDir>/src/$1",
|
||||
"^(\\.{1,2}/.*)\\.js$": "$1"
|
||||
},
|
||||
"transform": {
|
||||
"^.+\\.tsx?$": [
|
||||
"ts-jest",
|
||||
{
|
||||
"useESM": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
79
src/base/specs.test.ts
Normal file
79
src/base/specs.test.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import { specs, markdownTable, md2html } from './specs.js';
|
||||
import { sync as exists } from '@polymech/fs/exists';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
describe('specs', () => {
|
||||
describe('md2html', () => {
|
||||
it('should convert markdown to html', () => {
|
||||
const markdown = '# Hello\n\nThis is a test';
|
||||
const html = md2html(markdown);
|
||||
expect(html).toBe('<h1 id="hello">Hello</h1>\n<p>This is a test</p>');
|
||||
});
|
||||
|
||||
it('should handle tables', () => {
|
||||
const markdown = '| Header 1 | Header 2 |\n|----------|----------|\n| Cell 1 | Cell 2 |';
|
||||
const html = md2html(markdown);
|
||||
expect(html).toContain('<table>');
|
||||
expect(html).toContain('<th>Header 1</th>');
|
||||
expect(html).toContain('<td>Cell 1</td>');
|
||||
});
|
||||
});
|
||||
|
||||
describe('markdownTable', () => {
|
||||
it('should create a basic markdown table', () => {
|
||||
const table = [
|
||||
['Header 1', 'Header 2'],
|
||||
['Cell 1', 'Cell 2']
|
||||
];
|
||||
const result = markdownTable(table);
|
||||
expect(result).toBe('| Header 1 | Header 2 |\n| -------- | -------- |\n| Cell 1 | Cell 2 |');
|
||||
});
|
||||
|
||||
it('should handle empty cells', () => {
|
||||
const table = [
|
||||
['Header 1', 'Header 2'],
|
||||
['Cell 1', '']
|
||||
];
|
||||
const result = markdownTable(table);
|
||||
expect(result).toBe('| Header 1 | Header 2 |\n| -------- | -------- |\n| Cell 1 | |');
|
||||
});
|
||||
|
||||
it('should handle null/undefined values', () => {
|
||||
const table = [
|
||||
['Header 1', 'Header 2'],
|
||||
['Cell 1', null],
|
||||
['Cell 3', undefined]
|
||||
];
|
||||
const result = markdownTable(table);
|
||||
expect(result).toBe('| Header 1 | Header 2 |\n| -------- | -------- |\n| Cell 1 | |\n| Cell 3 | |');
|
||||
});
|
||||
|
||||
it('should handle custom alignment', () => {
|
||||
const table = [
|
||||
['Header 1', 'Header 2'],
|
||||
['Cell 1', 'Cell 2']
|
||||
];
|
||||
const result = markdownTable(table, { align: ['l', 'r'] });
|
||||
expect(result).toBe('| Header 1 | Header 2 |\n| :------- | -------: |\n| Cell 1 | Cell 2 |');
|
||||
});
|
||||
});
|
||||
|
||||
describe('specs', () => {
|
||||
it('should return empty string for non-existent file', () => {
|
||||
const result = specs('non-existent-file.xlsx');
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
it('should process valid xlsx file', () => {
|
||||
// Note: This test requires a valid xlsx file in the test directory
|
||||
// You might want to create a test fixture file
|
||||
const testFile = 'test/fixtures/test.xlsx';
|
||||
if (exists(testFile)) {
|
||||
const result = specs(testFile);
|
||||
expect(result).toBeTruthy();
|
||||
expect(typeof result).toBe('string');
|
||||
expect(result.length).toBeGreaterThan(0);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -4,56 +4,51 @@ import { sync as exists } from '@polymech/fs/exists'
|
||||
import pkg from 'showdown'
|
||||
const { Converter } = pkg
|
||||
|
||||
export const md2html = (content) => {
|
||||
export const md2html = (content: string): string => {
|
||||
let converter = new Converter({ tables: true });
|
||||
converter.setOption('literalMidWordUnderscores', 'true');
|
||||
return converter.makeHtml(content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef MarkdownTableOptions
|
||||
* @property {string|null|Array.<string|null|undefined>} [align]
|
||||
* @property {boolean} [padding=true]
|
||||
* @property {boolean} [delimiterStart=true]
|
||||
* @property {boolean} [delimiterStart=true]
|
||||
* @property {boolean} [delimiterEnd=true]
|
||||
* @property {boolean} [alignDelimiters=true]
|
||||
* @property {(value: string) => number} [stringLength]
|
||||
*/
|
||||
interface MarkdownTableOptions {
|
||||
align?: string | null | Array<string | null | undefined>;
|
||||
padding?: boolean;
|
||||
delimiterStart?: boolean;
|
||||
delimiterEnd?: boolean;
|
||||
alignDelimiters?: boolean;
|
||||
stringLength?: (value: string) => number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a table from a matrix of strings.
|
||||
*
|
||||
* from : https://github.com/wooorm/markdown-table/blob/main/index.js
|
||||
*
|
||||
*
|
||||
*
|
||||
* @param {Array.<Array.<string|null|undefined>>} table
|
||||
* @param {MarkdownTableOptions} [options]
|
||||
* @returns {string}
|
||||
*/
|
||||
export const markdownTable = (table, options: any = {}) => {
|
||||
|
||||
export const markdownTable = (table: (string | null | undefined)[][], options: MarkdownTableOptions = {}): string => {
|
||||
const align = (options.align || []).concat()
|
||||
const stringLength = options.stringLength || defaultStringLength
|
||||
/** @type {Array<number>} Character codes as symbols for alignment per column. */
|
||||
const alignments = []
|
||||
const alignments: number[] = []
|
||||
/** @type {Array<Array<string>>} Cells per row. */
|
||||
const cellMatrix = []
|
||||
const cellMatrix: string[][] = []
|
||||
/** @type {Array<Array<number>>} Sizes of each cell per row. */
|
||||
const sizeMatrix = []
|
||||
const sizeMatrix: number[][] = []
|
||||
/** @type {Array<number>} */
|
||||
const longestCellByColumn = []
|
||||
const longestCellByColumn: number[] = []
|
||||
let mostCellsPerRow = 0
|
||||
let rowIndex = -1
|
||||
|
||||
// This is a superfluous loop if we don’t align delimiters, but otherwise we’d
|
||||
// This is a superfluous loop if we don't align delimiters, but otherwise we'd
|
||||
// do superfluous work when aligning, so optimize for aligning.
|
||||
while (++rowIndex < table.length) {
|
||||
/** @type {Array<string>} */
|
||||
const row = []
|
||||
const row: string[] = []
|
||||
/** @type {Array<number>} */
|
||||
const sizes = []
|
||||
const sizes: number[] = []
|
||||
let columnIndex = -1
|
||||
|
||||
if (table[rowIndex].length > mostCellsPerRow) {
|
||||
@ -100,9 +95,9 @@ export const markdownTable = (table, options: any = {}) => {
|
||||
// Inject the alignment row.
|
||||
columnIndex = -1
|
||||
/** @type {Array<string>} */
|
||||
const row = []
|
||||
const row: string[] = []
|
||||
/** @type {Array<number>} */
|
||||
const sizes = []
|
||||
const sizes: number[] = []
|
||||
|
||||
while (++columnIndex < mostCellsPerRow) {
|
||||
const code = alignments[columnIndex]
|
||||
@ -148,14 +143,14 @@ export const markdownTable = (table, options: any = {}) => {
|
||||
|
||||
rowIndex = -1
|
||||
/** @type {Array<string>} */
|
||||
const lines = []
|
||||
const lines: string[] = []
|
||||
|
||||
while (++rowIndex < cellMatrix.length) {
|
||||
const row = cellMatrix[rowIndex]
|
||||
const sizes = sizeMatrix[rowIndex]
|
||||
columnIndex = -1
|
||||
/** @type {Array<string>} */
|
||||
const line = []
|
||||
const line: string[] = []
|
||||
|
||||
while (++columnIndex < mostCellsPerRow) {
|
||||
const cell = row[columnIndex] || ''
|
||||
@ -188,7 +183,7 @@ export const markdownTable = (table, options: any = {}) => {
|
||||
|
||||
if (
|
||||
options.padding !== false &&
|
||||
// Don’t add the opening space if we’re not aligning and the cell is
|
||||
// Don't add the opening space if we're not aligning and the cell is
|
||||
// empty: there will be a closing space.
|
||||
!(options.alignDelimiters === false && cell === '') &&
|
||||
(options.delimiterStart !== false || columnIndex)
|
||||
@ -232,7 +227,7 @@ export const markdownTable = (table, options: any = {}) => {
|
||||
* @param {string|null|undefined} [value]
|
||||
* @returns {string}
|
||||
*/
|
||||
function serialize(value) {
|
||||
function serialize(value: string | null | undefined): string {
|
||||
return value === null || value === undefined ? '' : String(value)
|
||||
}
|
||||
|
||||
@ -240,7 +235,7 @@ function serialize(value) {
|
||||
* @param {string} value
|
||||
* @returns {number}
|
||||
*/
|
||||
function defaultStringLength(value) {
|
||||
function defaultStringLength(value: string): number {
|
||||
return value.length
|
||||
}
|
||||
|
||||
@ -248,7 +243,7 @@ function defaultStringLength(value) {
|
||||
* @param {string|null|undefined} value
|
||||
* @returns {number}
|
||||
*/
|
||||
function toAlignment(value) {
|
||||
function toAlignment(value: string | null | undefined): number {
|
||||
const code = typeof value === 'string' ? value.codePointAt(0) : 0
|
||||
|
||||
return code === 67 /* `C` */ || code === 99 /* `c` */
|
||||
@ -260,13 +255,12 @@ function toAlignment(value) {
|
||||
: 0
|
||||
}
|
||||
|
||||
|
||||
export const specs = (path: string) => {
|
||||
export const specs = (path: string): string => {
|
||||
if (!path || !exists(path)) {
|
||||
return '';
|
||||
} else {
|
||||
let data = parse(path) as any;
|
||||
data[0].data = data[0].data.filter((d) => !!d.length);
|
||||
data[0].data = data[0].data.filter((d: any[]) => !!d.length);
|
||||
data = markdownTable(data[0].data);
|
||||
const ret = md2html(data);
|
||||
return ret
|
||||
|
||||
@ -25,7 +25,7 @@
|
||||
{ "flag": "friendly", "text": "be friendly and approachable" }
|
||||
],
|
||||
"content": [
|
||||
{ "flag": "spellCheck", "text": "spell check the text, fix any errors" },
|
||||
{ "flag": "spellCheck", "text": "spell & grammar fix the text," },
|
||||
{ "flag": "removeEmojis", "text": "remove emojis" },
|
||||
{ "flag": "removePersonalPrefs", "text": "remove personal preferences or biases" },
|
||||
{ "flag": "removeRedundancy", "text": "remove redundancy, eg: we attached the files" },
|
||||
@ -33,7 +33,8 @@
|
||||
],
|
||||
"moderation": [
|
||||
{ "flag": "mafiaFilter", "text": "remove references to preciousplastic, bazar and Discord" },
|
||||
{ "flag": "deprogramming", "text": "remove any brain/green washing, eg: sustainable, circular, recycling ... inflated prospects" }
|
||||
{ "flag": "deprogramming", "text": "remove any brain/green washing, eg: sustainable, circular, recycling ... inflated prospects" },
|
||||
{ "flag": "emptiness", "text": "Rewrite the following text to remove any inflated or empty language, sugar-coated filler, and needless repetition. The result should be concise, direct, and preserve only the essential ideas." }
|
||||
],
|
||||
"context": [
|
||||
{ "flag": "makerTutorials", "text": "Context: howto tutorials, for makers" },
|
||||
@ -46,7 +47,7 @@
|
||||
"defaults": {
|
||||
"tone": ["formal"],
|
||||
"content": ["spellCheck", "removeEmojis", "removePersonalPrefs", "shorten"],
|
||||
"moderation": ["mafiaFilter", "deprogramming"],
|
||||
"moderation": ["mafiaFilter", "deprogramming", "emptiness"],
|
||||
"context": ["makerTutorials", "units"],
|
||||
"format": ["markdown"]
|
||||
}
|
||||
|
||||
133
src/model/filters.test.ts
Normal file
133
src/model/filters.test.ts
Normal file
@ -0,0 +1,133 @@
|
||||
import './test-setup.js';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
shortenUrl,
|
||||
renderLinks,
|
||||
filterBannedPhrases,
|
||||
replaceWords,
|
||||
applyFilters,
|
||||
default_filters_plain,
|
||||
default_filters_markdown,
|
||||
item_path
|
||||
} from './filters.js';
|
||||
|
||||
describe('filters', () => {
|
||||
describe('item_path', () => {
|
||||
it('should generate correct path from item', () => {
|
||||
const item = { data: { slug: 'test-slug' } };
|
||||
expect(item_path(item)).toBe('/howto/test-slug');
|
||||
});
|
||||
});
|
||||
|
||||
describe('shortenUrl', () => {
|
||||
it('should remove www. prefix and trailing slashes', () => {
|
||||
expect(shortenUrl('https://www.example.com/path/')).toBe('example.com/path');
|
||||
});
|
||||
|
||||
it('should handle URLs without www. prefix', () => {
|
||||
expect(shortenUrl('https://example.com/path')).toBe('example.com/path');
|
||||
});
|
||||
|
||||
it('should handle invalid URLs gracefully', () => {
|
||||
expect(shortenUrl('invalid-url')).toBe('invalid-url');
|
||||
});
|
||||
|
||||
it('should handle URLs with query parameters', () => {
|
||||
expect(shortenUrl('https://example.com/path?param=value')).toBe('example.com/path?param=value');
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderLinks', () => {
|
||||
it('should render non-blacklisted links', () => {
|
||||
const input = 'Check out https://example.com';
|
||||
const expected = 'Check out <a class="text-orange-600 underline" href="https://example.com" target="_blank" rel="noopener noreferrer">example.com</a>';
|
||||
expect(renderLinks(input)).toBe(expected);
|
||||
});
|
||||
|
||||
it('should replace blacklisted links with [Link Removed]', () => {
|
||||
const input = 'Check out https://preciousplastic.com';
|
||||
expect(renderLinks(input)).toBe('Check out [Link Removed]');
|
||||
});
|
||||
|
||||
it('should handle multiple links in text', () => {
|
||||
const input = 'Check out https://example.com and https://preciousplastic.com';
|
||||
const result = renderLinks(input);
|
||||
expect(result).toContain('example.com');
|
||||
expect(result).toContain('[Link Removed]');
|
||||
});
|
||||
});
|
||||
|
||||
describe('filterBannedPhrases', () => {
|
||||
it('should replace banned words with [filtered]', () => {
|
||||
const input = 'The wizard used magic2';
|
||||
const expected = 'The [filtered] used [filtered]';
|
||||
expect(filterBannedPhrases(input)).toBe(expected);
|
||||
});
|
||||
|
||||
it('should handle case-insensitive matching', () => {
|
||||
const input = 'The WIZARD used MAGIC2';
|
||||
const expected = 'The [filtered] used [filtered]';
|
||||
expect(filterBannedPhrases(input)).toBe(expected);
|
||||
});
|
||||
|
||||
it('should not replace partial matches', () => {
|
||||
const input = 'The wizardry used magic2.0';
|
||||
const expected = 'The wizardry used [filtered].0';
|
||||
expect(filterBannedPhrases(input)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('replaceWords', () => {
|
||||
it('should replace words according to wordReplaceMap', () => {
|
||||
const input = 'I need a Router for my Car';
|
||||
const expected = 'I need a CNC Router for my tufftuff';
|
||||
expect(replaceWords(input)).toBe(expected);
|
||||
});
|
||||
|
||||
it('should handle multi-word replacements', () => {
|
||||
const input = 'I need a laptop stand';
|
||||
expect(replaceWords(input)).toBe('I need a laptoppie');
|
||||
});
|
||||
|
||||
it('should handle case-insensitive matching', () => {
|
||||
const input = 'I need a ROUTER for my CAR';
|
||||
const expected = 'I need a CNC Router for my tufftuff';
|
||||
expect(replaceWords(input)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('applyFilters', () => {
|
||||
it('should apply plain text filters in sequence', async () => {
|
||||
const input = 'Check out https://example.com with the wizard Router';
|
||||
const result = await applyFilters(input, default_filters_plain);
|
||||
expect(result).toContain('example.com');
|
||||
expect(result).toContain('[filtered]');
|
||||
expect(result).toContain('CNC Router');
|
||||
});
|
||||
|
||||
it('should apply markdown filters in sequence', async () => {
|
||||
const input = 'Check out [example](https://example.com) with the wizard Router';
|
||||
const result = await applyFilters(input, default_filters_markdown);
|
||||
expect(result).toContain('example');
|
||||
expect(result).toContain('[filtered]');
|
||||
expect(result).toContain('CNC Router');
|
||||
});
|
||||
|
||||
it('should handle empty input', async () => {
|
||||
expect(await applyFilters('')).toBe('');
|
||||
});
|
||||
|
||||
it('should handle custom filter array', async () => {
|
||||
const customFilters = [filterBannedPhrases];
|
||||
const input = 'The wizard used magic2';
|
||||
const expected = 'The [filtered] used [filtered]';
|
||||
expect(await applyFilters(input, customFilters)).toBe(expected);
|
||||
});
|
||||
|
||||
it('should handle markdown links with blacklisted URLs', async () => {
|
||||
const input = 'Check out [example](https://preciousplastic.com)';
|
||||
const result = await applyFilters(input, default_filters_markdown);
|
||||
expect(result).toBe('Check out [Link Removed]');
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,5 +1,6 @@
|
||||
export * from './howto-model.js'
|
||||
import { HOWTO_ROOT } from "config/config.js";
|
||||
import { filterMarkdownLinks } from "../base/markdown.js";
|
||||
|
||||
// Types and interfaces
|
||||
interface Item {
|
||||
@ -46,10 +47,10 @@ export const wordReplaceMap: Readonly<Record<string, string>> = {
|
||||
*/
|
||||
export const shortenUrl = (url: string): string => {
|
||||
try {
|
||||
const { hostname, pathname } = new URL(url);
|
||||
const { hostname, pathname, search } = new URL(url);
|
||||
const cleanHost = hostname.replace(/^www\./, '');
|
||||
const cleanPath = pathname.replace(/\/$/, '');
|
||||
return `${cleanHost}${decodeURIComponent(cleanPath)}`;
|
||||
return `${cleanHost}${decodeURIComponent(cleanPath)}${search}`;
|
||||
} catch (error) {
|
||||
console.warn(`Invalid URL provided to shortenUrl: ${url}`);
|
||||
return url;
|
||||
@ -94,18 +95,25 @@ export const replaceWords = (text: string): string =>
|
||||
text
|
||||
);
|
||||
|
||||
export const default_filters: FilterFunction[] = [
|
||||
export const default_filters_plain: FilterFunction[] = [
|
||||
renderLinks,
|
||||
filterBannedPhrases,
|
||||
replaceWords
|
||||
] as const;
|
||||
|
||||
export const default_filters_markdown: FilterFunction[] = [
|
||||
(text: string) => filterMarkdownLinks(text, urlBlacklist.map(url => ({ pattern: url, replacement: "[Link Removed]" }))),
|
||||
filterBannedPhrases,
|
||||
replaceWords
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Applies all filters to the input text in sequence
|
||||
* @param text - The text to filter
|
||||
* @param filters - Array of filter functions to apply
|
||||
* @returns Promise resolving to the filtered text
|
||||
*/
|
||||
export async function applyFilters(text: string = '',filters: FilterFunction[] = default_filters): Promise<string> {
|
||||
export async function applyFilters(text: string = '',filters: FilterFunction[] = default_filters_plain): Promise<string> {
|
||||
return filters.reduce(
|
||||
async (promise, filterFn) => {
|
||||
const currentText = await promise;
|
||||
|
||||
@ -43,7 +43,7 @@ import {
|
||||
|
||||
import { env, logger } from '@/base/index.js'
|
||||
|
||||
import { applyFilters, default_filters, FilterFunction } from './filters.js'
|
||||
import { applyFilters, default_filters_plain, FilterFunction } from './filters.js'
|
||||
import { TemplateContext, buildPrompt, LLMConfig, createTemplates } from '@/base/kbot-templates.js';
|
||||
import { template_filter } from '@/base/kbot.js'
|
||||
export const item_path = (item: IHowto) => `${HOWTO_ROOT()}/${item.slug}`
|
||||
@ -157,7 +157,7 @@ export const defaults = async (data: any, cwd: string, root: string) => {
|
||||
const commons = async (text: string): Promise<string> => {
|
||||
return await template_filter(text, 'simple', TemplateContext.COMMONS);
|
||||
}
|
||||
const content = async (str: string, filters: FilterFunction[] = default_filters) => await applyFilters(str, filters)
|
||||
const content = async (str: string, filters: FilterFunction[] = default_filters_plain) => await applyFilters(str, filters)
|
||||
const to_github = async (item: IHowto) => {
|
||||
const itemDir = item_path(item)
|
||||
// Create README.md with all content
|
||||
@ -264,7 +264,7 @@ const to_astro = async (item: IHowto) => {
|
||||
' <article class="max-w-4xl mx-auto px-4 py-8">',
|
||||
` <h1 class="text-4xl font-bold mb-8">{title}</h1>`,
|
||||
'',
|
||||
item.cover_image ? ` <Image src={import('./${item.cover_image.name}')} alt={title} class="w-full rounded-lg shadow-lg mb-8" />` : '',
|
||||
item.cover_image ? ` <Image src={import('./${sanitizeFilename(item.cover_image.name)}')} alt={title} class="w-full rounded-lg shadow-lg mb-8" />` : '',
|
||||
'',
|
||||
` <div class="prose prose-lg max-w-none mb-8">`,
|
||||
` <p>{description}</p>`,
|
||||
@ -279,7 +279,7 @@ const to_astro = async (item: IHowto) => {
|
||||
` <Fragment set:html={step.text} />`,
|
||||
` </div>`,
|
||||
// Add step images if any using Astro's Image component
|
||||
...step.images.map(img => ` <Image src={import('./${img.name}')} alt="${img.name}" class="w-full rounded-lg shadow-md mb-6" />`)
|
||||
...step.images.map(img => ` <Image src={import('./${sanitizeFilename(img.name)}')} alt="${img.name}" class="w-full rounded-lg shadow-md mb-6" />`)
|
||||
].join('\n')),
|
||||
' </div>',
|
||||
' </article>',
|
||||
|
||||
6
src/model/test-setup.ts
Normal file
6
src/model/test-setup.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { vi } from 'vitest';
|
||||
|
||||
// Mock the config module
|
||||
vi.mock('config/config.js', () => ({
|
||||
HOWTO_ROOT: () => '/howto'
|
||||
}));
|
||||
8
test-results/.last-run.json
Normal file
8
test-results/.last-run.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"status": "failed",
|
||||
"failedTests": [
|
||||
"a30a6eba6312f6b87ea5-e616cd2fb7265e6dfcb6",
|
||||
"a30a6eba6312f6b87ea5-46ce57013b5ef020f943",
|
||||
"a30a6eba6312f6b87ea5-9a0353411a5d89034f24"
|
||||
]
|
||||
}
|
||||
129
test/fixtures/test.xlsx
vendored
Normal file
129
test/fixtures/test.xlsx
vendored
Normal file
@ -0,0 +1,129 @@
|
||||
PK ! [Content_Types].xml<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
|
||||
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
|
||||
<Default Extension="xml" ContentType="application/xml"/>
|
||||
<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
|
||||
<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>
|
||||
<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>
|
||||
</Types>PK ! _rels/.rels<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
||||
<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>
|
||||
<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>
|
||||
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>
|
||||
</Relationships>PK ! xl/workbook.xml<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
|
||||
<fileVersion appName="xl" lastEdited="5" lowestEdited="5" rupBuild="9303"/>
|
||||
<workbookPr defaultThemeVersion="124226"/>
|
||||
<bookViews>
|
||||
<workbookView xWindow="240" yWindow="105" windowWidth="14805" windowHeight="8010"/>
|
||||
</bookViews>
|
||||
<sheets>
|
||||
<sheet name="Sheet1" sheetId="1" r:id="rId1"/>
|
||||
</sheets>
|
||||
<calcPr calcId="145621"/>
|
||||
</workbook>PK ! xl/worksheets/sheet1.xml<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
|
||||
<dimension ref="A1:B3"/>
|
||||
<sheetViews>
|
||||
<sheetView tabSelected="1" workbookViewId="0">
|
||||
<selection activeCell="A1" sqref="A1"/>
|
||||
</sheetView>
|
||||
</sheetViews>
|
||||
<sheetFormatPr defaultRowHeight="15"/>
|
||||
<sheetData>
|
||||
<row r="1" spans="1:2">
|
||||
<c r="A1" t="s">
|
||||
<v>Header 1</v>
|
||||
</c>
|
||||
<c r="B1" t="s">
|
||||
<v>Header 2</v>
|
||||
</c>
|
||||
</row>
|
||||
<row r="2" spans="1:2">
|
||||
<c r="A2" t="s">
|
||||
<v>Cell 1</v>
|
||||
</c>
|
||||
<c r="B2" t="s">
|
||||
<v>Cell 2</v>
|
||||
</c>
|
||||
</row>
|
||||
<row r="3" spans="1:2">
|
||||
<c r="A3" t="s">
|
||||
<v>Cell 3</v>
|
||||
</c>
|
||||
<c r="B3" t="s">
|
||||
<v>Cell 4</v>
|
||||
</c>
|
||||
</row>
|
||||
</sheetData>
|
||||
<pageMargins left="0.7" right="0.7" top="0.75" bottom="0.75" header="0.3" footer="0.3"/>
|
||||
</worksheet>PK ! xl/styles.xml<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
|
||||
<fonts count="1">
|
||||
<font>
|
||||
<sz val="11"/>
|
||||
<name val="Calibri"/>
|
||||
<family val="2"/>
|
||||
<scheme val="minor"/>
|
||||
</font>
|
||||
</fonts>
|
||||
<fills count="2">
|
||||
<fill>
|
||||
<patternFill patternType="none"/>
|
||||
</fill>
|
||||
<fill>
|
||||
<patternFill patternType="gray125"/>
|
||||
</fill>
|
||||
</fills>
|
||||
<borders count="1">
|
||||
<border>
|
||||
<left/>
|
||||
<right/>
|
||||
<top/>
|
||||
<bottom/>
|
||||
<diagonal/>
|
||||
</border>
|
||||
</borders>
|
||||
<cellStyleXfs count="1">
|
||||
<xf numFmtId="0" fontId="0" fillId="0" borderId="0"/>
|
||||
</cellStyleXfs>
|
||||
<cellXfs count="1">
|
||||
<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>
|
||||
</cellXfs>
|
||||
<cellStyles count="1">
|
||||
<cellStyle name="Normal" xfId="0" builtinId="0"/>
|
||||
</cellStyles>
|
||||
<dxfs count="0"/>
|
||||
<tableStyles count="0" defaultTableStyle="TableStyleMedium2" defaultPivotStyle="PivotStyleLight16"/>
|
||||
</styleSheet>PK ! docProps/core.xml<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<dc:creator>Test User</dc:creator>
|
||||
<cp:lastModifiedBy>Test User</cp:lastModifiedBy>
|
||||
<dcterms:created xsi:type="dcterms:W3CDTF">2024-01-01T00:00:00Z</dcterms:created>
|
||||
<dcterms:modified xsi:type="dcterms:W3CDTF">2024-01-01T00:00:00Z</dcterms:modified>
|
||||
</cp:coreProperties>PK ! docProps/app.xml<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">
|
||||
<Application>Microsoft Excel</Application>
|
||||
<DocSecurity>0</DocSecurity>
|
||||
<ScaleCrop>false</ScaleCrop>
|
||||
<HeadingPairs>
|
||||
<vt:vector size="2" baseType="variant">
|
||||
<vt:variant>
|
||||
<vt:lpstr>Worksheets</vt:lpstr>
|
||||
</vt:variant>
|
||||
<vt:variant>
|
||||
<vt:i4>1</vt:i4>
|
||||
</vt:variant>
|
||||
</vt:vector>
|
||||
</HeadingPairs>
|
||||
<TitlesOfParts>
|
||||
<vt:vector size="1" baseType="lpstr">
|
||||
<vt:lpstr>Sheet1</vt:lpstr>
|
||||
</vt:vector>
|
||||
</TitlesOfParts>
|
||||
<Company>Test Company</Company>
|
||||
<LinksUpToDate>false</LinksUpToDate>
|
||||
<SharedDoc>false</SharedDoc>
|
||||
<HyperlinksChanged>false</HyperlinksChanged>
|
||||
<AppVersion>16.0300</AppVersion>
|
||||
</Properties>PK !
|
||||
Loading…
Reference in New Issue
Block a user