<jupyter_start><jupyter_text>Can render basic content<jupyter_code>ExtensibleModule(module='''
export function renderFunction(args) {
    const div = document.createElement('div');
    div.style.background = 'lime';
    div.textContent = 'passed';
    args.node.appendChild(div);
}
''')<jupyter_output><empty_output><jupyter_text>Can use `waitUntil` to pause processing of subsequent outputs<jupyter_code>from IPython.display import Javascript
display(Javascript('window.nextOutputRendered = false'))
display(ExtensibleModule(module='''
export async function renderFunction(options) {
    const promise = new Promise((resolve) => setTimeout(resolve, 1000));
    options.data.waitUntil(promise);
    await promise;
    if (window.nextOutputRendered) {
        const div = document.createElement('div');
        div.style.background = 'red';
        div.textContent = 'failed: expected subsequent outputs to wait for this.';
        options.node.appendChild(div);
    } else {
        const div = document.createElement('div');
        div.style.background = '#88FF88';
        div.textContent = 'passed';
        options.node.appendChild(div);
    }
}
'''))
display(Javascript('window.nextOutputRendered = true'))<jupyter_output><empty_output><jupyter_text>Can access data of different mime types<jupyter_code>data = ExtensibleModule(module='''
export function renderFunction(options) {
    const plainText = options.data.data['text/plain'];
    if (plainText != 'the text value') {
        const div = document.createElement('div');
        div.style.background = 'red';
        div.textContent = `Expected the text to be "the text value" but was ${plainText}`;
        options.node.appendChild(div);
    } else {
        const div = document.createElement('div');
        div.style.background = 'lime';
        div.textContent = 'passed';
        options.node.appendChild(div);
    }
}
''')
data.data['text/plain'] = 'the text value'
data<jupyter_output><empty_output><jupyter_text>Does not leak implementation details<jupyter_code>ExtensibleModule(module='''
export function renderFunction(options) {
    const keys = Object.keys(options).sort();
    function fail(reason) {
        const div = document.createElement('div');
        div.style.background = 'red';
        div.textContent = reason;
        options.node.appendChild(div);
        throw new Error('failed');
    }
    
    function assertOnlyHasKeys(object, expected) {
        const actual = Object.keys(object).sort();
        expected = expected.sort();
        if (actual.length != expected.length) {
            fail(`Expected arrays to be equal: [${actual.join(',')}], [${expected.join(',')}]`);
        }
        for (let i = 0; i < actual.length; ++i) {
            if (actual[i] != expected[i]) {
                fail(`Expected arrays to be equal: [${actual.join(',')}], [${expected.join(',')}]`);
            }
        }
    }
    assertOnlyHasKeys(options, ['createComm', 'data', 'dataUpdates', 'node', 'nodeUpdates']);
    assertOnlyHasKeys(options.data, ['data', 'metadata', 'waitUntil']);
    const div = document.createElement('div');
    div.style.background = '#88FF88';
    div.textContent = 'passed';
    options.node.appendChild(div);
}
''')
import uuid

comm_opened = False
def my_comm_open(comm, message):
    comm.send({'passed': True})

get_ipython().kernel.comm_manager.register_target('my_comm_channel', my_comm_open)

module = ExtensibleModule(module='''
export async function renderFunction(options) {
    const status = document.createElement('div');
    status.style.background = 'red';
    status.textContent = 'No comms';
    options.node.appendChild(status);
        
    const commTargetName = options.data.data['application/vnd.notebook.json'].comm_target_name;
    const comms = await options.createComm(commTargetName);
    processMessages(comms);
    
    async function processMessages(comms) {
        let passed = false;
        for await (const message of comms.messages) {
            if (message.data.passed) {
                passed = true;
                comms.close();
            }
        }
        if (passed) {
            status.style.background = 'lime';
            status.textContent = 'passed';
        }
    }
}
''')
module.data['application/vnd.notebook.json'] = {
    'comm_target_name': 'my_comm_channel'
}
module<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.14164759725400458, 'fraction_numerical': 0.002974828375286041, 'mean_word_length': 3.3841524573721165, 'pattern_counts': {'":': 0, '<': 18, '<?xml version=': 0, '>': 18, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '4', 'dir': 'jupyter-structured-clean-dedup', 'id': '568131', 'license': 'Apache-2.0', 'n_tokens_mistral': 1295, 'n_tokens_neox': 1266, 'n_words': 258, 'path': 'tests.ipynb', 'repo_name': 'datalayer-contrib/jupyterlab-renderer-module', 'size': '4370'}