test(integrations/client/packages): batch 2-4 unit coverage + zip-slip guard extraction

Batch 2-4 of the test-strategy rollout. Test-only except one minimal,
behaviour-preserving extraction in file.utils.ts. All suites green:
server 82 suites/836+1todo, editor-ext 86, mcp 270, client (new files) 86.

integrations (server):
- file.utils.ts: extract pure `isEntryPathSafe(entryName, targetDir)` from
  extractZipInternal so the zip-slip/path-traversal guard is unit-testable;
  call site rerouted, behaviour identical (only a warn-message string merged).
- file.utils.zip-safety.spec.ts: traversal/strip/__MACOSX/prefix-confusion
  cases (mutation-resistant: fails if containment loses the path.sep).
- import-formatter / import.utils / table-utils / export utils / import.service
  extractTitleAndRemoveHeading: pure import/export transforms, Notion/XWiki
  formatting, table colspan widths (idempotent), slug/link rewriting.

client:
- safeRedirectPath: open-redirect guard, every reject branch independently.
- buildChatMarkdown (fence anti-breakout), label-colors, normalize-label,
  share tree build, page URL builders, notification time-grouping (fake clock).

packages:
- editor-ext: deriveFootnoteId golden table, parseHtmlEmbedHeight crafted
  values, orphan footnote extraction.
- mcp: deriveFootnoteId parity (drift guard vs editor-ext), applyTextEdits
  idempotency + cross-block replaceAll, diffDocs/summarizeChange on reorder.

Reviewed (APPROVE): extraction behaviour-preserving, assertions mutation-resistant.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
claude_code
2026-06-21 18:22:15 +03:00
parent f8e8ada581
commit 0b2af34029
20 changed files with 2495 additions and 17 deletions
@@ -0,0 +1,105 @@
import { load, CheerioAPI, Cheerio } from 'cheerio';
import { normalizeTableColumnWidths } from './table-utils';
/**
* Unit tests for normalizeTableColumnWidths: it writes a `colwidth` attribute
* onto the first-row cells of every <table>, deriving widths from a <colgroup>
* or the first row, accounting for colspan, and falling back to a default
* per-column width (150px) when no pixel widths are present. Re-running the
* transform on its own output must be a no-op (idempotent).
*/
const DEFAULT = 150;
function run(html: string): { $: CheerioAPI; $root: Cheerio<any> } {
const $ = load(html);
const $root = $.root();
normalizeTableColumnWidths($, $root);
return { $, $root };
}
function firstRowColwidths($root: Cheerio<any>): (string | undefined)[] {
return $root
.find('table')
.first()
.find('> tbody > tr, > thead > tr, > tr')
.first()
.children('td, th')
.map((_, el) => (el as any).attribs?.colwidth)
.get();
}
describe('normalizeTableColumnWidths', () => {
it('applies colgroup <col width> to the first-row cells', () => {
const html =
'<table>' +
'<colgroup><col width="120"><col width="80"></colgroup>' +
'<tbody><tr><td>a</td><td>b</td></tr></tbody>' +
'</table>';
const { $root } = run(html);
expect(firstRowColwidths($root)).toEqual(['120', '80']);
});
it('falls back to first-row cell widths when there is no colgroup', () => {
const html =
'<table><tbody>' +
'<tr><td style="width: 200px">a</td><td width="90">b</td></tr>' +
'</tbody></table>';
const { $root } = run(html);
expect(firstRowColwidths($root)).toEqual(['200', '90']);
});
it('splits a colspan width across the spanned columns', () => {
// colspan=2 with width 100 => each derived column ~50, the spanning cell
// then gets the joined slice "50,50".
const html =
'<table><tbody>' +
'<tr><td colspan="2" width="100">merged</td></tr>' +
'</tbody></table>';
const { $root } = run(html);
expect(firstRowColwidths($root)).toEqual(['50,50']);
});
it('ignores em/% widths (treated as no width) and applies the default', () => {
const html =
'<table><tbody>' +
'<tr><td style="width: 10em">a</td><td style="width: 50%">b</td></tr>' +
'</tbody></table>';
const { $root } = run(html);
expect(firstRowColwidths($root)).toEqual([String(DEFAULT), String(DEFAULT)]);
});
it('applies the default per-column width to a markdown-style table with no widths', () => {
const html =
'<table><tbody>' +
'<tr><td>a</td><td>b</td><td>c</td></tr>' +
'<tr><td>1</td><td>2</td><td>3</td></tr>' +
'</tbody></table>';
const { $root } = run(html);
expect(firstRowColwidths($root)).toEqual([
String(DEFAULT),
String(DEFAULT),
String(DEFAULT),
]);
});
it('is idempotent: re-running on its own output changes nothing', () => {
const html =
'<table>' +
'<colgroup><col width="120"><col width="80"></colgroup>' +
'<tbody><tr><td>a</td><td>b</td></tr></tbody>' +
'</table>';
const { $, $root } = run(html);
const afterFirst = $root.html();
// second pass
normalizeTableColumnWidths($, $root);
expect($root.html()).toBe(afterFirst);
expect(firstRowColwidths($root)).toEqual(['120', '80']);
});
});