Skip to content
Merged
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
120 changes: 120 additions & 0 deletions tests/font-fidelity.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,123 @@ test("splitCssFontFamilies", async () => {
assert.deepEqual(splitCssFontFamilies(""), []);
assert.deepEqual(splitCssFontFamilies(" "), []);
});

test("FontFidelity constructor options", async () => {
const { FontFidelity, POWERPOINT_FONT_FALLBACKS } = await loadFontFidelityModule();

const customFallbacks = { 'Custom Font': ['Fallback1', 'sans-serif'] };
const fidelity = new FontFidelity({ fontFallbacks: customFallbacks });

const fallbacks = fidelity.getRendererFallbacks();
assert.deepEqual(fallbacks['Custom Font'], ['Fallback1', 'sans-serif']);
assert.deepEqual(fallbacks['Aptos'], POWERPOINT_FONT_FALLBACKS['Aptos']);
});

test("FontFidelity.prototype.measureText delegates to measureTextFn and safely fallbacks", async () => {
const { FontFidelity } = await loadFontFidelityModule();

const mockMeasureText = (text, fontFamily, fontSizePx) => {
if (text === "invalid") return NaN;
if (text === "negative") return -1;
return text.length * fontSizePx;
};

const fidelity = new FontFidelity({
measureText: mockMeasureText,
isFontAvailable: () => true
});

// Delegates properly
assert.equal(fidelity.measureText("hello", "Arial", 10), 50);

// Safely falls back on NaN
assert.equal(fidelity.measureText("invalid", "Arial", 10), 7 * 10 * 0.6);

// Safely falls back on negative
assert.equal(fidelity.measureText("negative", "Arial", 10), 8 * 10 * 0.6);
});

class MockElement {
constructor(tagName) {
this.tagName = tagName;
this.attributes = new Map();
this.children = [];
}
getAttribute(name) {
return this.attributes.get(name) || null;
}
setAttribute(name, value) {
this.attributes.set(name, value);
}
getElementsByTagName(name) {
let results = [];
for (const child of this.children) {
if (name === '*' || child.tagName === name) {
results.push(child);
}
results.push(...child.getElementsByTagName(name));
}
return results;
}
}

test("FontFidelity.prototype.applySvgSubstitutions substitutes fonts and styles", async () => {
const { FontFidelity } = await loadFontFidelityModule();

const fidelity = new FontFidelity({
isFontAvailable: (font) => font.toLowerCase() === "arial" || font.toLowerCase() === "sans-serif"
});

const svg = new MockElement("svg");
svg.setAttribute("font-family", "Unknown Font");

const textElement = new MockElement("text");
textElement.setAttribute("style", "font-family: 'Another Unknown'; color: red;");
svg.children.push(textElement);

const substitutions = fidelity.applySvgSubstitutions(svg);

// Check svg attributes
assert.equal(svg.getAttribute("data-native-powerpoint-requested-font"), "Unknown Font");
assert.equal(svg.getAttribute("data-native-powerpoint-font-substitution"), "Arial");
assert.ok(svg.getAttribute("font-family").includes("Arial"));

// Check text element style
assert.equal(textElement.getAttribute("data-native-powerpoint-requested-font"), "Another Unknown");
assert.equal(textElement.getAttribute("data-native-powerpoint-font-substitution"), "Arial");
assert.ok(textElement.getAttribute("style").includes("font-family:"));
assert.ok(textElement.getAttribute("style").includes("color: red;"));
Comment on lines +140 to +141

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Assert the rewritten inline font value

The input style already contains both font-family: and color: red;, so these assertions still pass if applySvgSubstitutions() stops rewriting inline font families altogether. Assert that the resulting style contains the resolved Arial stack (or the exact expected style) so this test detects the regression it is intended to cover.

Useful? React with πŸ‘Β / πŸ‘Ž.


// Check substitutions array
assert.equal(substitutions.length, 2);
// Sorted by requested font string
assert.equal(substitutions[0].requested, "Another Unknown");
assert.equal(substitutions[0].substitute, "Arial");
assert.equal(substitutions[1].requested, "Unknown Font");
assert.equal(substitutions[1].substitute, "Arial");
});

test("FontFidelity caches isFontAvailableFn results", async () => {
const { FontFidelity } = await loadFontFidelityModule();

let callCount = 0;
const mockIsFontAvailable = (font) => {
callCount++;
return true;
};

const fidelity = new FontFidelity({
isFontAvailable: mockIsFontAvailable
});

fidelity.measureText("hello", "CustomCacheFont", 10);
const callsAfterFirst = callCount;

// Should not trigger again
fidelity.measureText("hello", "CustomCacheFont", 10);
assert.equal(callCount, callsAfterFirst);

// Same font with quotes and mixed casing should hit cache
fidelity.measureText("hello", "'customcachefont'", 10);
assert.equal(callCount, callsAfterFirst);
Comment on lines +168 to +174

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Exercise the availability cache independently

These repeated calls only exercise resolveFont()'s resolution cache: it normalizes CustomCacheFont, stores the whole resolution, and returns it before consulting isFontAvailable() again. Consequently, this test remains green even if the separate availability cache is removed or broken. Use distinct requested-font resolution keys that share a fallback candidate, then verify that the availability callback is invoked only once for that shared candidate.

Useful? React with πŸ‘Β / πŸ‘Ž.

});
Loading