test/unit/HtmlFormatter.js
import {arrayBuilderFactory} from 'tag-messageformat';
import HtmlFormatter from '../../src/HtmlFormatter';
import IntlMessageFormat from "tag-messageformat";
import Formatter from "../../src/Formatter";
describe('HtmlFormatter', () => {
let config;
beforeEach(() => {
config = {
locale: 'en',
messages: {
no_args: 'Hello, World!'
},
};
});
describe('constructor', () => {
it('should throw if defaultHtmlElement is not a function or string', () => {
expect(() => new HtmlFormatter('en', { defaultHtmlElement: 0 }))
.toThrow(/must be either a string or function/);
expect(() => new HtmlFormatter('en', { defaultHtmlElement: new Date() }))
.toThrow(/must be either a string or function/);
expect(() => new HtmlFormatter('en', { defaultHtmlElement: {} }))
.toThrow(/must be either a string or function/);
expect(() => new HtmlFormatter('en', { defaultHtmlElement: [] }))
.toThrow(/must be either a string or function/);
});
});
describe('get options()', () => {
it('should return formatter options', () => {
const opts = {
formats: {},
messages: {}
};
const fmt = new HtmlFormatter('af', opts);
expect(Object.keys(fmt.options)).toHaveLength(15);
});
});
describe('changeLocale', () => {
it('should return a IntlFormatter instance', () => {
const fmt = new HtmlFormatter();
const newFmt = fmt.changeLocale('en');
expect(newFmt instanceof HtmlFormatter).toBe(true);
});
});
describe('format', () => {
let fmt;
describe('`htmlMessageBuilderFactory` option', () => {
function HtmlElementArrayBuilder() {
this._elements = [];
}
HtmlElementArrayBuilder.prototype.append = function (value) {
this._elements.push(value);
};
HtmlElementArrayBuilder.prototype.appendOpeningTag = function (tagName) {
this.append(`<${tagName}>`);
};
HtmlElementArrayBuilder.prototype.appendClosingTag = function (tagName) {
this.append(`</${tagName}>`);
};
HtmlElementArrayBuilder.prototype.appendChildren = function (value) {
this.append(value);
};
HtmlElementArrayBuilder.prototype.build = function () {
return this._elements;
};
function htmlElementArrayBuilderFactory() {
return new HtmlElementArrayBuilder();
}
it('formats a message using the default message builder', () => {
const fmt = new HtmlFormatter('en', {
messages: config.messages,
htmlElementBuilderFactory: htmlElementArrayBuilderFactory,
htmlMessageBuilderFactory: arrayBuilderFactory
});
expect(fmt.messageElement({id: 'no_args'})).toEqual(['<span>', [config.messages.no_args], '</span>']);
});
it('formats a message using a custom message builder', () => {
const fmt = new HtmlFormatter('en', { messages: config.messages });
expect(fmt.messageElement({id: 'no_args'}, {}, {
htmlElementBuilderFactory: htmlElementArrayBuilderFactory,
messageBuilderFactory: arrayBuilderFactory
})).toEqual(['<span>', [config.messages.no_args], '</span>']);
});
});
describe('`defaultHtmlElement` function option', () => {
let renderMethodFn;
beforeEach(() => {
renderMethodFn = jest.fn((txt) => `!${txt}!`);
const { locale, ...formatterOpts } = config;
fmt = new HtmlFormatter(locale, {...formatterOpts,
defaultHtmlElement: renderMethodFn
});
});
it('formats a message', () => {
expect(fmt.messageElement({id: 'no_args'})).toBe(`!${config.messages.no_args}!`);
expect(renderMethodFn.mock.calls).toHaveLength(1);
});
it('formats a defaultMessage value', () => {
const msgId = '__default_only__';
const defaultMsg = 'a default message';
const { locale, ...formatterOpts } = config;
fmt = new HtmlFormatter(locale, {...formatterOpts,
defaultHtmlElement: renderMethodFn,
defaultMessages: { [msgId]: defaultMsg }
});
expect(fmt.messageElement({id: msgId})).toBe(`!${defaultMsg}!`);
});
it('formats a htmlMessage', () => {
expect(fmt.htmlMessageElement({id: 'no_args'})).toBe(`!${config.messages.no_args}!`);
expect(renderMethodFn.mock.calls).toHaveLength(1);
});
it('formats a date', () => {
const df = new Intl.DateTimeFormat(config.locale);
const now = new Date().getTime();
expect(fmt.dateElement(now)).toBe(`!${df.format(now)}!`);
expect(renderMethodFn.mock.calls).toHaveLength(1);
});
it('formats a time', () => {
const df = new Intl.DateTimeFormat(config.locale, {
hour: 'numeric',
minute: 'numeric',
});
const now = new Date().getTime();
expect(fmt.timeElement(now)).toBe(`!${df.format(now)}!`);
expect(renderMethodFn.mock.calls).toHaveLength(1);
});
it('formats a relative value', () => {
const now = new Date().getTime();
expect(fmt.relativeElement(now)).toBe(`!now!`);
expect(renderMethodFn.mock.calls).toHaveLength(1);
});
it('formats a number', () => {
expect(fmt.numberElement(1)).toBe(`!1!`);
expect(renderMethodFn.mock.calls).toHaveLength(1);
});
});
describe('`htmlElements.*` function options', () => {
let renderMethodFn;
beforeEach(() => {
renderMethodFn = jest.fn((txt) => `!${txt}!`);
const { locale, ...formatterOpts } = config;
fmt = new HtmlFormatter(locale, {...formatterOpts,
htmlElements: {
message: renderMethodFn,
htmlMessage: renderMethodFn,
date: renderMethodFn,
time: renderMethodFn,
number: renderMethodFn,
relative: renderMethodFn,
}
});
});
it('formats a message', () => {
expect(fmt.messageElement({id: 'no_args'})).toBe(`!${config.messages.no_args}!`);
expect(renderMethodFn.mock.calls).toHaveLength(1);
});
it('formats a htmlMessage', () => {
expect(fmt.htmlMessageElement({id: 'no_args'})).toBe(`!${config.messages.no_args}!`);
expect(renderMethodFn.mock.calls).toHaveLength(1);
});
it('formats a date', () => {
const df = new Intl.DateTimeFormat(config.locale);
const now = new Date().getTime();
expect(fmt.dateElement(now)).toBe(`!${df.format(now)}!`);
expect(renderMethodFn.mock.calls).toHaveLength(1);
});
it('formats a time', () => {
const df = new Intl.DateTimeFormat(config.locale, {
hour: 'numeric',
minute: 'numeric',
});
const now = new Date().getTime();
expect(fmt.timeElement(now)).toBe(`!${df.format(now)}!`);
expect(renderMethodFn.mock.calls).toHaveLength(1);
});
it('formats a relative value', () => {
const now = new Date().getTime();
expect(fmt.relativeElement(now)).toBe(`!now!`);
expect(renderMethodFn.mock.calls).toHaveLength(1);
});
it('formats a number', () => {
expect(fmt.numberElement(1)).toBe(`!1!`);
expect(renderMethodFn.mock.calls).toHaveLength(1);
});
});
describe('`defaultHtmlElement` string option', () => {
beforeEach(() => {
const { locale, ...formatterOpts } = config;
fmt = new HtmlFormatter(locale, {...formatterOpts,
defaultHtmlElement: 'span'
});
});
it('formats a message', () => {
expect(fmt.messageElement({id: 'no_args'})).toBe(`<span>${config.messages.no_args}</span>`);
});
it('formats a htmlMessage', () => {
expect(fmt.htmlMessageElement({id: 'no_args'})).toBe(`<span>${config.messages.no_args}</span>`);
});
it('formats a date', () => {
const df = new Intl.DateTimeFormat(config.locale);
const now = new Date().getTime();
expect(fmt.dateElement(now)).toBe(`<span>${df.format(now)}</span>`);
});
it('formats a time', () => {
const df = new Intl.DateTimeFormat(config.locale, {
hour: 'numeric',
minute: 'numeric',
});
const now = new Date().getTime();
expect(fmt.timeElement(now)).toBe(`<span>${df.format(now)}</span>`);
});
it('formats a relative value', () => {
const now = new Date().getTime();
expect(fmt.relativeElement(now)).toBe(`<span>now</span>`);
});
it('formats a number', () => {
expect(fmt.numberElement(1)).toBe(`<span>1</span>`);
});
});
describe('`htmlElements.*` string options', () => {
beforeEach(() => {
const { locale, ...formatterOpts } = config;
fmt = new HtmlFormatter(locale, {...formatterOpts,
htmlElements: {
message: 'span',
htmlMessage: 'span',
date: 'span',
time: 'span',
number: 'span',
relative: 'span',
}
});
});
it('formats a message', () => {
expect(fmt.messageElement({id: 'no_args'})).toBe(`<span>${config.messages.no_args}</span>`);
});
it('formats a htmlMessage', () => {
expect(fmt.htmlMessageElement({id: 'no_args'})).toBe(`<span>${config.messages.no_args}</span>`);
});
it('formats a date', () => {
const df = new Intl.DateTimeFormat(config.locale);
const now = new Date().getTime();
expect(fmt.dateElement(now)).toBe(`<span>${df.format(now)}</span>`);
});
it('formats a time', () => {
const df = new Intl.DateTimeFormat(config.locale, {
hour: 'numeric',
minute: 'numeric',
});
const now = new Date().getTime();
expect(fmt.timeElement(now)).toBe(`<span>${df.format(now)}</span>`);
});
it('formats a relative value', () => {
const now = new Date().getTime();
expect(fmt.relativeElement(now)).toBe(`<span>now</span>`);
});
it('formats a number', () => {
expect(fmt.numberElement(1)).toBe(`<span>1</span>`);
});
});
describe('messageBuilderContextFactory option', () => {
let TestContext, mockId, mockMessage, mockFormatted;
beforeEach(() => {
TestContext = function () {};
mockId = TestContext.prototype.id = jest.fn();
mockMessage = TestContext.prototype.message = jest.fn();
mockFormatted = TestContext.prototype.formatted = jest.fn(msg => msg);
});
it('formats message text using formatter message builder context', () => {
const {locale, ...opts} = config;
const ctx = new TestContext();
fmt = new Formatter(locale, {
...opts,
messageBuilderContextFactory: (id) => { ctx.id(id); return ctx; }});
const mf = new IntlMessageFormat(opts.messages.no_args, locale);
expect(fmt.message({id: 'no_args'})).toEqual(mf.format());
expect(mockId.mock.calls).toHaveLength(1);
expect(mockId.mock.calls[0][0]).toEqual('no_args');
expect(mockMessage.mock.calls).toHaveLength(1);
expect(mockFormatted.mock.calls).toHaveLength(1);
});
it('formats a message using method message builder context', () => {
const {locale, messages} = config;
const mf = new IntlMessageFormat(messages.no_args, locale);
const ctx = new TestContext();
expect(fmt.message({id: 'no_args'}, {}, { messageBuilderContextFactory: (id) => { ctx.id(id); return ctx; } }))
.toEqual(mf.format());
expect(mockId.mock.calls).toHaveLength(1);
expect(mockId.mock.calls[0][0]).toEqual('no_args');
expect(mockMessage.mock.calls).toHaveLength(1);
expect(mockFormatted.mock.calls).toHaveLength(1);
});
});
});
describe('extend', () => {
it('should still support static `create` syntax', () => {
expect(HtmlFormatter.create({}) instanceof HtmlFormatter).toBe(false);
});
it('should create a new Formatter class', () => {
const { locale } = config;
const now = new Date().getTime();
const CustomFormatter = HtmlFormatter.extend({
message: 'm',
messageElement: 'me',
htmlMessage: 'h',
htmlMessageElement: 'he',
date: 'd',
dateElement: 'de',
time: 't',
timeElement: 'te',
number: 'n',
numberElement: 'ne',
relative: 'r',
relativeElement: 're',
plural: 'p'
});
const customFormatter = new CustomFormatter(locale, config);
expect(customFormatter.m).toBeDefined();
expect(customFormatter.m({id: 'no_args'})).toBe(customFormatter.message({id: 'no_args'}));
expect(customFormatter.me).toBeDefined();
expect(customFormatter.me({id: 'no_args'})).toBe(customFormatter.messageElement({id: 'no_args'}));
expect(customFormatter.h).toBeDefined();
expect(customFormatter.h({id: 'no_args'})).toBe(customFormatter.htmlMessage({id: 'no_args'}));
expect(customFormatter.he).toBeDefined();
expect(customFormatter.he({id: 'no_args'})).toBe(customFormatter.htmlMessageElement({id: 'no_args'}));
expect(customFormatter.d).toBeDefined();
expect(customFormatter.d(now)).toBe(customFormatter.date(now));
expect(customFormatter.de).toBeDefined();
expect(customFormatter.de(now)).toBe(customFormatter.dateElement(now));
expect(customFormatter.t).toBeDefined();
expect(customFormatter.t(now)).toBe(customFormatter.time(now));
expect(customFormatter.te).toBeDefined();
expect(customFormatter.te(now)).toBe(customFormatter.timeElement(now));
expect(customFormatter.n).toBeDefined();
expect(customFormatter.n(0)).toBe(customFormatter.number(0));
expect(customFormatter.ne).toBeDefined();
expect(customFormatter.ne(0)).toBe(customFormatter.numberElement(0));
expect(customFormatter.r).toBeDefined();
expect(customFormatter.r(0)).toBe(customFormatter.relative(0));
expect(customFormatter.re).toBeDefined();
expect(customFormatter.re(0)).toBe(customFormatter.relativeElement(0));
expect(customFormatter.p).toBeDefined();
expect(customFormatter.p(1)).toBe(customFormatter.plural(1));
});
});
});