[gjs/ewlsh/whatwg-console: 4/4] Add Console tests




commit b119b45d78fb15b1c15b69274f62c3a610bc8ce2
Author: Evan Welsh <contact evanwelsh com>
Date:   Fri Aug 13 21:38:07 2021 -0700

    Add Console tests

 installed-tests/js/.eslintrc.yml  |   1 +
 installed-tests/js/matchers.js    |  36 +++++-
 installed-tests/js/meson.build    |   1 +
 installed-tests/js/testConsole.js | 257 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 294 insertions(+), 1 deletion(-)
---
diff --git a/installed-tests/js/.eslintrc.yml b/installed-tests/js/.eslintrc.yml
index cdf5cf9f..abc9c527 100644
--- a/installed-tests/js/.eslintrc.yml
+++ b/installed-tests/js/.eslintrc.yml
@@ -33,6 +33,7 @@ overrides:
   - files:
       - matchers.js
       - testCairoModule.js
+      - testConsole.js
       - testESModules.js
       - testEncoding.js
       - testGLibLogWriter.js
diff --git a/installed-tests/js/matchers.js b/installed-tests/js/matchers.js
index 6a2848f6..1e05828f 100644
--- a/installed-tests/js/matchers.js
+++ b/installed-tests/js/matchers.js
@@ -26,7 +26,41 @@ export function arrayLikeWithExactContents(elements) {
          * @returns {string}
          */
         jasmineToString() {
-            return `${JSON.stringify(elements)}`;
+            return `<arrayLikeWithExactContents(${
+                elements.constructor.name
+            }[${JSON.stringify(Array.from(elements))}]>)`;
+        },
+    };
+}
+
+/**
+ * A jasmine asymmetric matcher which compares a given string to an
+ * array-like object of bytes. The compared bytes are decoded using
+ * TextDecoder and then compared using jasmine.stringMatching.
+ *
+ * @param {string | RegExp} text the text or regular expression to compare decoded bytes to
+ * @param {string} [encoding] the encoding of elements
+ * @returns
+ */
+export function decodedStringMatching(text, encoding = 'utf-8') {
+    const matcher = jasmine.stringMatching(text);
+
+    return {
+        /**
+         * @param {ArrayLike<number>} compareTo an array of bytes to decode and compare to
+         * @returns {boolean}
+         */
+        asymmetricMatch(compareTo) {
+            const decoder = new TextDecoder(encoding);
+            const decoded = decoder.decode(new Uint8Array(Array.from(compareTo)));
+
+            return matcher.asymmetricMatch(decoded, []);
+        },
+        /**
+         * @returns {string}
+         */
+        jasmineToString() {
+            return `<decodedStringMatching(${text})>`;
         },
     };
 }
diff --git a/installed-tests/js/meson.build b/installed-tests/js/meson.build
index f85b9586..b42f3b20 100644
--- a/installed-tests/js/meson.build
+++ b/installed-tests/js/meson.build
@@ -241,6 +241,7 @@ endif
 # minijasmine flag
 
 modules_tests = [
+    'Console',
     'ESModules',
     'Encoding',
     'GLibLogWriter',
diff --git a/installed-tests/js/testConsole.js b/installed-tests/js/testConsole.js
new file mode 100644
index 00000000..43027439
--- /dev/null
+++ b/installed-tests/js/testConsole.js
@@ -0,0 +1,257 @@
+// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
+// SPDX-FileCopyrightText: 2021 Evan Welsh <contact evanwelsh com>
+
+// eslint-disable-next-line
+/// <reference types="jasmine" />
+
+import GLib from 'gi://GLib';
+import {DEFAULT_LOG_DOMAIN, Console} from 'console';
+
+import {decodedStringMatching} from './matchers.js';
+
+function objectContainingLogMessage(
+    message,
+    domain = DEFAULT_LOG_DOMAIN,
+    fields = {}
+) {
+    return jasmine.objectContaining({
+        MESSAGE: decodedStringMatching(message),
+        GLIB_DOMAIN: decodedStringMatching(domain),
+        ...fields,
+    });
+}
+
+describe('console', function () {
+    /** @type {jasmine.Spy<(_level: any, _fields: any) => any>} */
+    let writer_func;
+
+    /**
+     * @param {RegExp | string} message _
+     * @param {*} [logLevel] _
+     * @param {*} [domain] _
+     * @param {*} [fields] _
+     */
+    function expectLog(
+        message,
+        logLevel = GLib.LogLevelFlags.LEVEL_MESSAGE,
+        domain = DEFAULT_LOG_DOMAIN,
+        fields = {}
+    ) {
+        expect(writer_func).toHaveBeenCalledOnceWith(
+            logLevel,
+            objectContainingLogMessage(message, domain, fields)
+        );
+
+        // Always reset the calls, so that we can assert at the end that no
+        // unexpected messages were logged
+        writer_func.calls.reset();
+    }
+
+    beforeAll(function () {
+        writer_func = jasmine.createSpy(
+            'Console test writer func',
+            function (level, _fields) {
+                if (level === GLib.LogLevelFlags.ERROR)
+                    return GLib.LogWriterOutput.UNHANDLED;
+
+                return GLib.LogWriterOutput.HANDLED;
+            }
+        );
+
+        writer_func.and.callThrough();
+
+        // @ts-expect-error The existing binding doesn't accept any parameters because
+        // it is a raw pointer.
+        GLib.log_set_writer_func(writer_func);
+    });
+
+    beforeEach(function () {
+        writer_func.calls.reset();
+    });
+
+    it('has correct object tag', function () {
+        expect(console.toString()).toBe('[object console]');
+    });
+
+    it('logs a message', function () {
+        console.log('a log');
+
+        expect(writer_func).toHaveBeenCalledOnceWith(
+            GLib.LogLevelFlags.LEVEL_MESSAGE,
+            objectContainingLogMessage('a log')
+        );
+        writer_func.calls.reset();
+    });
+
+    it('logs a warning', function () {
+        console.warn('a warning');
+
+        expect(writer_func).toHaveBeenCalledOnceWith(
+            GLib.LogLevelFlags.LEVEL_WARNING,
+            objectContainingLogMessage('a warning')
+        );
+        writer_func.calls.reset();
+    });
+
+    it('logs an informative message', function () {
+        console.info('an informative message');
+
+        expect(writer_func).toHaveBeenCalledOnceWith(
+            GLib.LogLevelFlags.LEVEL_INFO,
+            objectContainingLogMessage('an informative message')
+        );
+        writer_func.calls.reset();
+    });
+
+    describe('console.clear()', function () {
+        it('clear can be called.', function () {
+            console.clear();
+        });
+
+        it('clear resets indentation.', function () {
+            console.group('a group');
+            expectLog('a group');
+            console.log('a log');
+            expectLog('  a log');
+            console.clear();
+            console.log('a log');
+            expectLog('a log');
+        });
+    });
+
+    // %s - string
+    // %d or %i - integer
+    // %f - float
+    // %o  - "optimal" object formatting
+    // %O - "generic" object formatting
+    // %c - "CSS" formatting (unimplemented by GJS)
+    describe('string replacement', function () {
+        const functions = {
+            log: GLib.LogLevelFlags.LEVEL_MESSAGE,
+            warn: GLib.LogLevelFlags.LEVEL_WARNING,
+            info: GLib.LogLevelFlags.LEVEL_INFO,
+            error: GLib.LogLevelFlags.LEVEL_CRITICAL,
+        };
+
+        Object.entries(functions).forEach(([fn, level]) => {
+            it(`console.${fn}() supports %s`, function () {
+                console[fn]('Does this %s substitute correctly?', 'modifier');
+                expectLog('Does this modifier substitute correctly?', level);
+            });
+
+            it(`console.${fn}() supports %d`, function () {
+                console[fn]('Does this %d substitute correctly?', 10);
+                expectLog('Does this 10 substitute correctly?', level);
+            });
+
+            it(`console.${fn}() supports %i`, function () {
+                console[fn]('Does this %i substitute correctly?', 26);
+                expectLog('Does this 26 substitute correctly?', level);
+            });
+
+            it(`console.${fn}() supports %f`, function () {
+                console[fn]('Does this %f substitute correctly?', 27.56331);
+                expectLog('Does this 27.56331 substitute correctly?', level);
+            });
+
+            it(`console.${fn}() supports %o`, function () {
+                console[fn]('Does this %o substitute correctly?', new Error());
+                expectLog(/Does this Error\n.*substitute correctly\?/s, level);
+            });
+
+            it(`console.${fn}() supports %O`, function () {
+                console[fn]('Does this %O substitute correctly?', new Error());
+                expectLog('Does this {} substitute correctly?', level);
+            });
+
+            it(`console.${fn}() ignores %c`, function () {
+                console[fn]('Does this %c substitute correctly?', 'modifier');
+                expectLog('Does this  substitute correctly?', level);
+            });
+
+            it(`console.${fn}() supports mixing substitutions`, function () {
+                console[fn](
+                    'Does this %s and the %f substitute correctly alongside %d?',
+                    'string',
+                    3.14,
+                    14
+                );
+                expectLog(
+                    'Does this string and the 3.14 substitute correctly alongside 14?',
+                    level
+                );
+            });
+
+            it(`console.${fn}() supports invalid numbers`, function () {
+                console[fn](
+                    'Does this support parsing %i incorrectly?',
+                    'a string'
+                );
+                expectLog('Does this support parsing NaN incorrectly?', level);
+            });
+
+            it(`console.${fn}() supports missing substitutions`, function () {
+                console[fn]('Does this support a missing %s substitution?');
+                expectLog(
+                    'Does this support a missing %s substitution?',
+                    level
+                );
+            });
+        });
+    });
+
+    describe('console.time()', function () {
+        it('ends correctly', function (done) {
+            console.time('testing time');
+
+            // console.time logs nothing.
+            expect(writer_func).not.toHaveBeenCalled();
+
+            setTimeout(() => {
+                console.timeLog('testing time');
+
+                expectLog(/testing time: (.*)ms/);
+
+                console.timeEnd('testing time');
+
+                expectLog(/testing time: (.*)ms/);
+
+                console.timeLog('testing time');
+
+                expectLog(
+                    "No time log found for label: 'testing time'.",
+                    GLib.LogLevelFlags.LEVEL_WARNING
+                );
+
+                done();
+            }, 10);
+        });
+
+        it("doesn't log initially", function (done) {
+            console.time('testing time');
+
+            // console.time logs nothing.
+            expect(writer_func).not.toHaveBeenCalled();
+
+            setTimeout(() => {
+                console.timeEnd('testing time');
+                expectLog(/testing time: (.*)ms/);
+
+                done();
+            }, 10);
+        });
+
+        afterEach(function () {
+            // Ensure we only got the log lines that we expected
+            expect(writer_func).not.toHaveBeenCalled();
+        });
+    });
+});
+
+describe('Console constructor', function () {
+    const console = new Console();
+
+    it('has correct object tag', function () {
+        expect(console.toString()).toBe('[object Console]');
+    });
+});


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]