where lang is the notebook's language", () => {
expect( codeEl.className ).toBe(`lang-${notebookLang}`)
})
it("has attribute data-language with the notebook's language", () => {
expect( codeEl.getAttribute('data-language') ).toBe(notebookLang)
})
it('has $source converted using codeHighlighter() as the innerHTML', () => {
expect( codeHighlighter ).toBeCalledWith(join(cell.source), expect.anything())
expect( codeEl.innerHTML ).toEqual(mockLastResult(codeHighlighter))
})
})
describe('when the notebook does not have metadata.language_info.name', () => {
const myNotebook: Notebook = { ...notebook, metadata: {} }
const notebookLang = 'python'
it('uses the default language: python', () => {
const result = renderer.renderSource(cell, myNotebook)
const codeEl = result.firstChild!.firstChild as HTMLElement
expect( codeEl.getAttribute('data-language') ).toBe(notebookLang)
expect( codeEl.classList ).toContain(`lang-${notebookLang}`)
expect( codeHighlighter ).toBeCalledWith(expect.anything(), notebookLang)
})
})
})
describe('.renderOutput', () => {
const cell = { ...fixtures.CodeCell, execution_count: null }
describe.each([
'renderDisplayData', 'renderExecuteResult', 'renderStream', 'renderError',
] as const)('with %s output', (funcName) => {
const type = funcName.replace('render', '')
const output = (fixtures as any)[type]
let result: HTMLElement
beforeEach(() => {
renderer[funcName] = rendererMock(type)
result = renderer.renderOutput(output, cell)
})
it('returns div.output', () => {
expect( result ).toMatchElement(
)
})
it(`returns element with the output rendered using .${funcName}() as the only child`, () => {
expect( renderer[funcName] ).toBeCalledWith(output)
expect( result.children ).toHtmlEqual([mockLastResult(renderer[funcName])!])
})
describe('when the cell has non-null execution_count', () => {
const cell = { ...fixtures.CodeCell, execution_count: 2 }
it('returns element with attributes data-execution-count and data-prompt-number', () => {
const result = renderer.renderOutput(output, cell)
expect( result.attributes ).toMatchObject({
'data-execution-count': String(cell.execution_count),
'data-prompt-number': String(cell.execution_count),
})
})
})
})
describe('with unsupported output type', () => {
const output = {
output_type: 'whatever',
} as any
const cell = {
...fixtures.CodeCell,
execution_count: null,
output: [output],
}
it('returns div with comment "Unsupported output type"', () => {
expect( renderer.renderOutput(output, cell) ).toHtmlEqual(
)
})
})
})
describe('.renderDisplayData', () => {
function displayDataWith (data: MimeBundle): DisplayData {
return { ...fixtures.DisplayData, data }
}
function withMimeData (
mimeType: string,
value: MultilineString,
fn: (output: DisplayData, value: MultilineString) => void,
): void {
describe(mimeType, () => {
describe('as a string', () => {
const data = join(value)
fn(displayDataWith({ [mimeType]: data }), data)
})
describe('as an array', () => {
const data = arrify(value)
fn(displayDataWith({ [mimeType]: data }), data)
})
})
}
describe('with single data of unsupported MIME type', () => {
const displayData = displayDataWith({ 'text/non-sense': 'whaat' })
it('returns div.empty-output', () => {
expect( renderer.renderDisplayData(displayData) ).toHtmlEqual(
)
})
})
describe('with single data of built-in MIME type', () => {
;['image/png', 'image/jpeg'].forEach(mimeType => {
withMimeData(mimeType, ['aW1hZ2Ug\n', 'ZGF0YQ=='], (output) => {
it('returns img.image-output with the data in the src attribute', () => {
expect( renderer.renderDisplayData(output) ).toHtmlEqual(
)
})
})
})
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
;([
/* mimeType | classes */
['image/svg+xml', ['svg-output'] ],
['text/svg+xml' , ['svg-output'] ],
['text/html' , ['html-output'] ],
['text/latex' , ['latex-output']],
] as Array<[string, string[]]>).forEach(([mimeType, classes]) => {
withMimeData(mimeType, 'data', (output, data) => {
it(`returns div${classes.map(x => `.${x}`)} with the data as content`, () => {
expect( renderer.renderDisplayData(output) ).toHtmlEqual(
{{__html: join(data) }}
)
})
})
})
withMimeData('text/markdown', ['Lorem\n', 'ipsum'], (output, data) => {
it('returns div.html-output with the data converted using markdownRenderer() as content', () => {
expect( renderer.renderDisplayData(output) ).toHtmlEqual(
{{__html: mockLastResult(markdownRenderer) }}
)
expect( markdownRenderer ).toBeCalledWith(join(data))
})
})
withMimeData('text/plain', '>_<', (output) => {
it('returns pre.text-output with html-escaped data', () => {
expect( renderer.renderDisplayData(output) ).toHtmlEqual(
{ '>_<' }
)
})
})
withMimeData('application/javascript', 'alert("Hello &!")', (output, data) => {
it('returns script with the data', () => {
expect( renderer.renderDisplayData(output) ).toHtmlEqual(
)
})
})
})
describe('with single data of non-built-in MIME type', () => {
withMimeData('text/custom', 'Lorem ipsum', (output, data) => {
it('renders the data using the associated external renderer', () => {
expect( renderer.renderDisplayData(output) ).toHtmlEqual(
mockLastResult(dataRenderers['text/custom'])!
)
expect( dataRenderers['text/custom'] ).toBeCalledWith(join(data))
})
})
})
describe('with multiple data', () => {
const mimeBundle = {
'text/plain': 'Lorem ipsum',
'text/html': 'Lorem ipsum
',
'text/unknown': '???',
}
const output = displayDataWith(mimeBundle)
it('renders the data of the MIME type with a higher priority', () => {
expect( renderer.renderDisplayData(output) ).toHtmlEqual(
{{__html: mimeBundle['text/html'] }}
)
})
test('the provided dataRenderers have higher priority than the built-ins', () => {
const mimeBundle = {
'text/custom': '>>Lorem ipsum<<',
'text/html': 'Lorem ipsum
',
}
const output = displayDataWith(mimeBundle)
expect( renderer.renderDisplayData(output) ).toHtmlEqual(
mockLastResult(dataRenderers['text/custom'])!
)
})
})
describe('when built with external renderer for the built-in type', () => {
const dataRenderer = rendererMock('DisplayData')
beforeEach(() => {
renderer = new NbRenderer(elementCreator, {
...rendererOpts,
dataRenderers: { 'text/plain': dataRenderer },
})
})
it('renders the data using the external renderer instead of the built-in', () => {
const data = 'allons-y!'
const output = displayDataWith({ 'text/plain': [data] })
expect( renderer.renderDisplayData(output) ).toBe(mockLastResult(dataRenderer))
expect( dataRenderer ).toBeCalledWith(data)
})
})
})
describe('.renderError', () => {
const error = fixtures.Error
const traceback = error.traceback.join('\n')
it('returns pre.error.pyerr with inner $traceback converted using ansiCodesRenderer', () => {
expect( renderer.renderError(error) ).toHtmlEqual(
{{__html: mockLastResult(ansiCodesRenderer) }}
)
expect( ansiCodesRenderer ).toBeCalledWith(traceback)
})
})
describe('.renderStream', () => {
eachMultilineVariant(fixtures.Stream, 'text', (stream) => {
const text = join(stream.text)
it('returns pre.$name with inner $text converted using ansiCodesRenderer', () => {
expect( renderer.renderStream(stream) ).toHtmlEqual(
{{__html: mockLastResult(ansiCodesRenderer) }}
)
expect( ansiCodesRenderer ).toBeCalledWith(text)
})
})
})
})
function eachMultilineVariant (
obj: T,
propName: K,
fn: (obj: T) => void,
): void {
const propValue = obj[propName]
describe(`when ${propName} is an array`,
() => fn({ ...obj, [propName]: arrify(propValue) }))
describe(`when ${propName} is a string`,
() => fn({ ...obj, [propName]: join(propValue) }))
}
const genStubElement = jest.fn((type: string) => {
const id = genStubElement.mock.calls.filter(args => args[0] === type).length
const el = document.createElement('stub')
el.setAttribute('type', type)
el.setAttribute('id', id.toString())
return el
})
function stubElement (type: string): HTMLElement {
return genStubElement(type)
}
function rendererMock (type: string) {
return jest.fn(() => stubElement(type))
}
function join (input: MultilineString): string {
return Array.isArray(input) ? input.join('') : input
}