<!DOCTYPE html>
<meta charset="utf-8">
<title>Reconnect to presentation success manual test</title>
<link rel="author" title="Marius Wessel" href="http://www.fokus.fraunhofer.de">
<link rel="author" title="Louay Bassbouss" href="http://www.fokus.fraunhofer.de">
<link rel="help" href="http://w3c.github.io/presentation-api/#dom-presentationrequest-reconnect">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="common.js"></script>
<style>iframe { display: none; }</style>

<p>Click the button below to start the manual test. Select a presentation device after the selection dialog is prompted.
    The test assumes that at least one presentation device is available. The test passes if a "PASS" result appears.</p>
<button id="startBtn">Start Test</button>
<iframe id="childFrame" src="support/iframe.html"></iframe>

<script>
    let receiverStack;
    add_completion_callback(() => {
        // overwrite a stack written in the test result
        if (receiverStack) {
            document.querySelector('#log pre').textContent = receiverStack;
        }
    });

    // disable timeout for manual tests
    setup({explicit_timeout: true});
    const startBtn = document.getElementById('startBtn');
    const childFrame = document.getElementById('childFrame');

    promise_test(t => {
        const startWatcher = new EventWatcher(t, startBtn, 'click');
        const messageWatcher = new EventWatcher(t, window, 'message');
        const request = new PresentationRequest(presentationUrls);
        let connection, eventWatcher;

        t.add_cleanup(() => {
            if (connection) {
                connection.onconnect = () => { connection.terminate(); }
                if (connection.state === 'closed')
                    request.reconnect(connection.id);
                else
                    connection.terminate();
            }
        });

        const waitForMessage = () => {
            return messageWatcher.wait_for('message').then(evt => {
                return evt.data.type === 'presentation-api' ? evt : waitForMessage();
            });
        };

        // handle a test result received from a nested browsing context
        const parseValue = value => {
            let r;

            // String
            if (r = value.match(/^(\(string\)\s+)?"(.*)"$/))
                return r[2];
            // Object
            else if (r = value.match(/^(\(object\)\s+)?object\s+"\[object\s+(.*)\]"$/))
                return window[r[2]].prototype;
            // undefined
            else if (value === "undefined")
                return undefined;
            // Number, boolean, null
            else {
                if (r = value.match(/^(\(\S+\)\s+)?(\S+)$/)) {
                    try {
                        return JSON.parse(r[2]);
                    } catch(e) {
                        return value;
                    }
                }
                else
                    return value;
            }
        };

        const parseResult = t.step_func(evt => {
            const result = evt.data;
            if (result.test.status === 0)
                return evt;

            receiverStack = result.test.stack;
            const message = result.test.message;
            let r = message.match(/^(assert_.*):\s+(.*)$/);
            if (r) {
                const assertion = r[1];
                const body = r[2];
                let args;
                if (assertion === 'assert_equals') {
                    if (r = body.match(/^((.*)\s+)?expected\s+((\(\S*\)\s+)?(\S+|(\S+\s+)?\".*\"))\s+but\s+got\s+((\(\S*\)\s+)?(\S+|(\S+\s+)?\".*\"))$/))
                        args = [parseValue(r[7]), parseValue(r[3]), r[2]];
                }
                else if (assertion === 'assert_true') {
                    if (r = body.match(/^((.*)\s+)?expected\s+(true|false)\s+got\s+(\S+|(\S+\s+)?\".*\")$/)) {
                        args = [parseValue(r[4]), r[2]];
                    }
                }
                else if (assertion === 'assert_unreached') {
                    if (r = body.match(/^((.*)\s+)?Reached\s+unreachable\s+code$/))
                        args = [r[2]];
                }
                if (args) {
                    window[assertion](args[0], args[1], args[2]);
                    return;
                }
            }
            // default
            assert_unreached('Test result received from a receiving user agent: ' + message + ': ');
        });

        return Promise.all([
            startWatcher.wait_for('click'),
            messageWatcher.wait_for('message')
        ]).then(() => {
            startBtn.disabled = true;
            let presentationId = null;
            return request.start();
        }).then(c => {
            connection = c;
            presentationId = connection.id;

            // No more user input needed, re-enable test timeout
            t.step_timeout(() => {
                t.force_timeout();
                t.done();
            }, 5000);

            eventWatcher = new EventWatcher(t, connection, ['connect', 'close', 'terminate']);

            return Promise.all([
                // Wait for "connect" event
                eventWatcher.wait_for('connect'),
                // Try to reconnect when the connection state is "connecting"
                request.reconnect(presentationId).then(c => {
                    assert_equals(c, connection, 'The promise is resolved with the existing presentation connection.');
                    assert_equals(c.state, "connecting", "The connection state remains 'connecting'.");
                    assert_equals(c.id, presentationId, "The presentation ID is not changed.");
                })
            ]);
        }).then(() => {
            // Try to reconnect when the connection state is "connected"
            return request.reconnect(presentationId);
        }).then(c => {
            assert_equals(c, connection, 'The promise is resolved with the existing presentation connection.');
            assert_equals(c.state, "connected", "The connection state remains 'connected'.");
            assert_equals(c.id, presentationId, "The presentation ID is not changed.");

            // Close connection and wait for "close" event
            connection.close();
            return eventWatcher.wait_for('close');
        }).then(() => {
            // Connection now closed, let's reconnect to it
            return request.reconnect(presentationId);
        }).then(c => {
            // Check the presentation connection in "connecting" state
            assert_equals(c, connection, 'The promise is resolved with the existing presentation connection.');
            connection = c;
            assert_equals(connection.state, "connecting", "The connection state is set to 'connecting'.");
            assert_equals(connection.id, presentationId, "Ids of old and new connections must be equal.");

            return eventWatcher.wait_for('connect');
        }).then(evt => {
            // Check the established presentation connection and its associated "connect" event
            assert_true(evt.isTrusted && !evt.bubbles && !evt.cancelable && evt instanceof Event, 'A simple event is fired.');
            assert_equals(evt.type, 'connect', 'The event name is "connect".');
            assert_equals(evt.target, connection, 'event.target is the presentation connection.');
            assert_equals(connection.state, 'connected', 'The presentation connection state is set to "connected".');

            // Request an iframe to reconnect the presentation with the current presentation ID
            childFrame.contentWindow.postMessage('reconnect?id=' + presentationId, '*');
            return waitForMessage().then(parseResult);
        }).then(() => {
            // Wait until state of each presentation connection is set to "terminated"
            connection.terminate();
            return Promise.all([ eventWatcher.wait_for('terminate'), waitForMessage().then(parseResult) ]);
        }).then(() => {
            // Try to reconnect to the presentation, while all presentation connection have already been terminated
            return promise_rejects_dom(t, 'NotFoundError', request.reconnect(presentationId),
                'Reconnecting to a terminated presentation rejects a promise with a "NotFoundError" exception.');
        });
    });
</script>
