import { CSSProcessedProps } from '@native-html/css-processor'; import HTMLModelRegistry from '../../model/HTMLModelRegistry'; import markersPrototype from '../../tree/markersPrototype'; import { TNodeDescriptor } from '../../tree/tree-types'; import { defaultStylesConfig } from '../defaults'; import { TStyles } from '../TStyles'; import { TStylesMerger } from '../TStylesMerger'; const emptyStyles = new TStyles(new CSSProcessedProps()); const modelRegistry = new HTMLModelRegistry(); function makeTNodeDescriptor( partial?: Partial ): TNodeDescriptor { return { attributes: {}, classes: [], domNode: null as any, hasClass(className) { return partial?.classes?.includes(className) || false; }, id: null, markers: Object.create(markersPrototype), tagName: null, ...partial }; } describe('TStylesMerger', () => { describe('buildStyles method', () => { it('should override id styles with inline styles', () => { const stylesMerger = new TStylesMerger( { ...defaultStylesConfig, idsStyles: { main: { color: 'red' } } }, modelRegistry ); expect( stylesMerger.buildStyles( 'color: blue', null, makeTNodeDescriptor({ id: 'main', tagName: 'div' }) ).nativeTextFlow.color ).toEqual('blue'); }); it('should override classes styles with inline styles', () => { const stylesMerger = new TStylesMerger( { ...defaultStylesConfig, classesStyles: { main: { color: 'red' } } }, modelRegistry ); expect( stylesMerger.buildStyles( 'color: blue', null, makeTNodeDescriptor({ classes: ['main'], tagName: 'div' }) ).nativeTextFlow.color ).toEqual('blue'); }); it('should override tags styles with inline styles', () => { const stylesMerger = new TStylesMerger( { ...defaultStylesConfig, tagsStyles: { div: { color: 'red' } } }, modelRegistry ); expect( stylesMerger.buildStyles( 'color: blue', null, makeTNodeDescriptor({ tagName: 'div' }) ).nativeTextFlow.color ).toEqual('blue'); }); it('should override tags styles with classes styles', () => { const stylesMerger = new TStylesMerger( { ...defaultStylesConfig, tagsStyles: { div: { color: 'red' } }, classesStyles: { main: { color: 'blue' } } }, modelRegistry ); expect( stylesMerger.buildStyles( '', null, makeTNodeDescriptor({ classes: ['main'], tagName: 'div' }) ).nativeTextFlow.color ).toEqual('blue'); }); it('should ignore unregistered classes', () => { const stylesMerger = new TStylesMerger( { ...defaultStylesConfig }, modelRegistry ); const styles = stylesMerger.buildStyles( '', null, makeTNodeDescriptor({ id: 'main', classes: ['content'], tagName: 'div' }) ); expect(styles).toStrictEqual(emptyStyles); }); it('should merge multiple classes', () => { const stylesMerger = new TStylesMerger( { ...defaultStylesConfig, classesStyles: { content: { color: 'blue' }, 'content--highlight': { backgroundColor: 'yellow' } } }, modelRegistry ); const styles = stylesMerger.buildStyles( '', null, makeTNodeDescriptor({ id: 'main', classes: ['content', 'content--highlight'], tagName: 'div' }) ); expect(styles.nativeTextFlow).toMatchObject({ color: 'blue' }); expect(styles.nativeBlockRet).toStrictEqual({ backgroundColor: 'yellow' }); }); it('should override leftmost classes properties with rightmost classes properties', () => { const stylesMerger = new TStylesMerger( { ...defaultStylesConfig, classesStyles: { content: { color: 'blue' }, 'content--highlight': { color: 'green' } } }, modelRegistry ); expect( stylesMerger.buildStyles( '', null, makeTNodeDescriptor({ id: 'main', classes: ['content', 'content--highlight'], tagName: 'div' }) ).nativeTextFlow ).toStrictEqual({ color: 'green' }); }); it('should override classes styles with id styles', () => { const stylesMerger = new TStylesMerger( { ...defaultStylesConfig, idsStyles: { main: { color: 'red' } }, classesStyles: { content: { color: 'blue' } } }, modelRegistry ); expect( stylesMerger.buildStyles( '', null, makeTNodeDescriptor({ id: 'main', classes: ['content'], tagName: 'div' }) ).nativeTextFlow ).toStrictEqual({ color: 'red' }); }); it('should retain parent flowed styles when no override exists', () => { const stylesMerger = new TStylesMerger( { ...defaultStylesConfig }, modelRegistry ); const parentStyles = new TStyles( stylesMerger.compileInlineCSS('color:red;') ); const styles = stylesMerger.buildStyles( '', parentStyles, makeTNodeDescriptor({ tagName: null }) ); expect(styles.nativeTextFlow.color).toStrictEqual('red'); }); it('should override parent flowed styles when override exists', () => { const stylesMerger = new TStylesMerger( { ...defaultStylesConfig, tagsStyles: { div: { color: 'blue' } } }, modelRegistry ); expect( stylesMerger.buildStyles( '', new TStyles(stylesMerger.compileInlineCSS('color:red;')), makeTNodeDescriptor({ tagName: 'div' }) ).nativeTextFlow.color ).toStrictEqual('blue'); }); describe('with enableUAStyles set to true', () => { describe('regarding
tags', () => { it('should default to UA styles with margins left and right when attribute "type" is unset', () => { const stylesMerger = new TStylesMerger( { ...defaultStylesConfig, enableUserAgentStyles: true }, modelRegistry ); const processedProps = stylesMerger.buildStyles( '', null, makeTNodeDescriptor({ tagName: 'blockquote' }) ); expect(processedProps.nativeBlockRet).toStrictEqual({ marginBottom: 16, marginTop: 16, marginLeft: 30, marginRight: 30 }); }); it('should default to UA styles with left border gray when attribute "type" is set to "cite"', () => { const stylesMerger = new TStylesMerger( { ...defaultStylesConfig, enableUserAgentStyles: true }, modelRegistry ); const processedProps = stylesMerger.buildStyles( '', null, makeTNodeDescriptor({ attributes: { type: 'cite' }, tagName: 'blockquote' }) ); expect(processedProps.nativeBlockRet).toStrictEqual({ borderLeftWidth: 2, borderLeftColor: '#CCC', marginBottom: 16, marginTop: 16, marginLeft: 30, marginRight: 30 }); }); }); describe('regarding tags which have no default UA tags', () => { it('should default to empty styles', () => { const stylesMerger = new TStylesMerger( { ...defaultStylesConfig, enableUserAgentStyles: true }, modelRegistry ); const processedProps = stylesMerger.buildStyles( '', null, makeTNodeDescriptor({ tagName: 'div' }) ); expect(processedProps.nativeBlockFlow).toStrictEqual({}); expect(processedProps.nativeBlockRet).toStrictEqual({}); }); }); it('should override UA styles with inline styles', () => { const stylesMerger = new TStylesMerger( { ...defaultStylesConfig, enableUserAgentStyles: true }, modelRegistry ); const processedProps = stylesMerger.buildStyles( 'margin-top: 0;', null, makeTNodeDescriptor({ tagName: 'p' }) ); expect(processedProps.nativeBlockRet.marginTop).toBe(0); }); describe('regarding tags', () => { it('should default to null UA styles when attribute "href" is unset', () => { const stylesMerger = new TStylesMerger( { ...defaultStylesConfig, enableUserAgentStyles: true }, modelRegistry ); const processedProps = stylesMerger.buildStyles( '', null, makeTNodeDescriptor({ tagName: 'a' }) ); expect(processedProps.nativeTextFlow).toStrictEqual({}); expect(processedProps.nativeTextRet).toStrictEqual({}); }); it('should default to UA styles with color #245dc1 (pale blue) and underlined text when attribute "href" is set', () => { const stylesMerger = new TStylesMerger( { ...defaultStylesConfig, enableUserAgentStyles: true }, modelRegistry ); const processedProps = stylesMerger.buildStyles( '', null, makeTNodeDescriptor({ attributes: { href: '' }, tagName: 'a' }) ); expect(processedProps.nativeTextFlow).toStrictEqual({ color: '#245dc1' }); expect(processedProps.nativeTextRet).toStrictEqual({ textDecorationLine: 'underline', textDecorationColor: '#245dc1' }); }); it('should have UA styles when attribute "href" is set overridden by user tag styles', () => { const stylesMerger = new TStylesMerger( { ...defaultStylesConfig, enableUserAgentStyles: true, tagsStyles: { a: { color: 'red', textDecorationColor: 'red' } } }, modelRegistry ); const processedProps = stylesMerger.buildStyles( '', null, makeTNodeDescriptor({ attributes: { href: 'https://domain.com' }, tagName: 'a' }) ); expect(processedProps.nativeTextFlow).toStrictEqual({ color: 'red' }); expect(processedProps.nativeTextRet).toStrictEqual({ textDecorationLine: 'underline', textDecorationColor: 'red' }); }); }); }); }); });