gpii-windows
Version:
Components of the GPII personalization infrastructure for use on Microsoft's "Windows" ™
329 lines (294 loc) • 10.7 kB
JavaScript
/*
* Tests for AppBar
*
* Copyright 2020 Raising the Floor - International
*
* Licensed under the New BSD license. You may not use this file except in
* compliance with this License.
*
* The R&D leading to these results received funding from the
* Department of Education - Grant H421A150005 (GPII-APCP). However,
* these results do not necessarily represent the policy of the
* Department of Education, and you should not assume endorsement by the
* Federal Government.
*
* You may obtain a copy of the License at
* https://github.com/GPII/universal/blob/master/LICENSE.txt
*/
"use strict";
var fluid = require("gpii-universal"),
kettle = fluid.require("kettle"),
ffi = fluid.require("ffi-napi");
kettle.loadTestingSupport();
var jqUnit = fluid.require("node-jqunit");
var gpii = fluid.registerNamespace("gpii");
require("../src/appBar.js");
fluid.registerNamespace("gpii.tests.windows.appBar");
fluid.defaults("gpii.tests.appBar.tests", {
gradeNames: ["fluid.test.testEnvironment"],
components: {
appBar: {
type: "gpii.windows.appBar",
options: {
invokers: {
getNativeWindowHandle: "gpii.tests.appBar.getNativeWindowHandle({that})",
hookWindowMessage: "fluid.identity",
unhookWindowMessage: "fluid.identity",
isReady: {
funcName: "fluid.identity",
args: ["{that}.ready"]
}
},
listeners: {
"onCreate.test_createWindow": {
priority: "first",
func: "gpii.tests.appBar.createWindow",
args: ["{that}"]
},
"onDestroy.test_destroyWindow": {
func: "gpii.tests.appBar.destroyWindow",
args: ["{that}"]
}
}
}
},
tester: {
type: "gpii.tests.appBar.testCaseHolder"
}
}
});
fluid.defaults("gpii.tests.appBar.testCaseHolder", {
gradeNames: ["fluid.test.testCaseHolder"],
modules: [{
name: "Task bar position",
tests: [{
expect: 1,
name: "Test getTaskbarPos",
sequence: [{
funcName: "gpii.tests.appBar.testTaskBarPos"
}]
}]
}, {
name: "Messages",
tests: [{
expect: 1,
name: "checking messages are being listened to",
sequence: [{
func: "jqUnit.assertDeepEq",
args: [
"The expected messages should be listened for",
"{that}.options.testData.expectedMessages",
"{appBar}.hookedMessages"
]
}]
}]
}, {
name: "Enable app bar",
tests: [{
expect: 1,
name: "checking the window is in the correct position",
sequence: [{
func: "{appBar}.enable"
}, {
funcName: "gpii.windows.user32.SetWindowPos",
args: ["{appBar}.hwnd", 0, 0, 0, 200, 200, 1]
}, {
task: "gpii.windows.waitForCondition",
args: ["{appBar}.isReady"],
resolve: "fluid.identity"
}, {
funcName: "gpii.tests.appBar.assertAppBarPos",
args: ["{appBar}.hwnd"]
}]
}]
}, {
name: "Disable app bar",
tests: [{
expect: 1,
name: "checking the window is in the correct position",
sequence: [{
func: "{appBar}.disable"
}, {
funcName: "jqUnit.assertDeepEq",
args: [
"Work area should have been restored",
"{that}.options.testData.originalWorkArea",
"@expand:gpii.tests.appBar.getWorkArea()"
]
}]
}]
}],
testData: {
expectedMessages: [
"@expand:gpii.windows.messages.registeredMessage(WM_GPII_APPBAR)",
gpii.windows.API_constants.WM_WINDOWPOSCHANGING,
gpii.windows.API_constants.WM_WINDOWPOSCHANGED,
gpii.windows.API_constants.WM_ACTIVATE
],
originalWorkArea: "@expand:gpii.tests.appBar.getWorkArea()"
}
});
/**
* Tests that getTaskbarPos works.
*/
gpii.tests.appBar.testTaskBarPos = function () {
jqUnit.expect(3);
var desktop = gpii.windows.display.getDesktopSize();
var tests = {
left: {
left: 0,
top: 0,
width: 100,
height: desktop.height
},
top: {
left: 0,
top: 0,
width: desktop.width,
height: 100
},
right: {
left: desktop.width - 100,
top: 0,
width: 100,
height: desktop.height
},
bottom: {
left: 0,
top: desktop.height - 100,
width: desktop.width,
height: 100
}
};
fluid.each(tests, function (rect, expect) {
var result = gpii.windows.appBar.getTaskbarPos(rect);
jqUnit.assert("getTaskbarPos should return the expected result", expect, result);
});
};
/**
* Checks that the app bar window is in the correct position - in a gap between the bottom of the work area and the top
* of the task bar.
*
* @param {Number} hwnd The app bar window handle.
*/
gpii.tests.appBar.assertAppBarPos = function (hwnd) {
jqUnit.expect(1);
// Check that the window is above the task bar
var rect = gpii.windows.getWindowRect(hwnd);
var screenBottom = gpii.windows.appBar.getWorkAreaBottom();
jqUnit.assertTrue("Window should be above taskbar", rect.bottom <= screenBottom);
// Check that the window is below the work area
var workArea = gpii.tests.appBar.getWorkArea();
jqUnit.assertTrue("Window should be beneath the work area", rect.top > workArea.bottom);
};
/**
* Gets the work area.
* @return {Rect} The work area rectangle.
*/
gpii.tests.appBar.getWorkArea = function () {
var workArea = new gpii.windows.RECT();
var success = gpii.windows.spi.systemParametersInfoWPtr.SystemParametersInfoW(
gpii.windows.spi.actions.SPI_GETWORKAREA, 0, workArea.ref(), 0);
if (!success) {
jqUnit.fail(gpii.windows.win32errorText("SystemParametersInfoW(SPI_GETWORKAREA)", success));
}
return {
left: workArea.left,
top: workArea.top,
right: workArea.right,
bottom: workArea.bottom
};
};
/**
* Implements electron's getNativeWindowHandle() - returns the window handle in a 32 bit buffer.
* @param {Component} that The gpii.windows.appBar instance.
* @return {Buffer} The window handle.
*/
gpii.tests.appBar.getNativeWindowHandle = function (that) {
var buf = Buffer.alloc(4);
buf.writeUInt32LE(that.hwnd);
return buf;
};
/**
* Destroys the test window.
* @param {Component} that The gpii.windows.appBar instance.
*/
gpii.tests.appBar.destroyWindow = function (that) {
if (that.hwnd) {
gpii.windows.user32.DestroyWindow(that.messageWindow);
that.hwnd = null;
}
};
/**
* Creates a window, for testing the app bar functionality.
* (taken from gpii.windows.messages.createMessageWindow)
*
* @param {Component} that The gpii.windows.appBar instance
*/
gpii.tests.appBar.createWindow = function (that) {
if (that.hwnd) {
fluid.fail("Test window already created.");
}
var className = gpii.windows.stringToWideChar("gpii-appbar-test");
// Create the Window Class for the window
var cls = new gpii.windows.WNDCLASSW();
cls.ref().fill(0);
cls.lpszClassName = className;
// Create a pointer to the window procedure function.
var callback = ffi.Callback(gpii.windows.types.HANDLE,
[gpii.windows.types.HANDLE, gpii.windows.types.UINT, gpii.windows.types.UINT, gpii.windows.types.PVOID],
function (hwnd, msg, wParam, lParam) {
if (msg === that.WM_GPII_APPBAR && wParam === gpii.windows.API_constants.ABN_POSCHANGED) {
that.ready = true;
}
// Pass the parameters as buffers, like Electron does.
var wp = Buffer.alloc(4);
var lp = Buffer.alloc(4);
wp.writeUInt32LE(wParam);
lp.writeUInt32LE(lParam.address());
gpii.windows.appBar.windowMessage(that, hwnd, msg, wp, lp);
return gpii.windows.user32.DefWindowProcW(hwnd, msg, wParam, lParam);
});
cls.lpfnWndProc = callback;
// Keep a reference to the function pointer, otherwise the GC will free it. Because 'callback' points to the
// function that windows calls whenever there is a window message, the buffers need to be kept alive for as long
// as GPII is running.
// Additionally, this value also contains the "cif" buffer (internal to ffi-napi) which also needs to be
// referenced. See GPII-3445.
gpii.tests.appBar.windowProcPointer = function () {
callback();
};
var result = gpii.windows.user32.RegisterClassW(cls.ref());
if (!result) {
fluid.fail("RegisterClass failed");
}
// Start the message loop (needs to be done before creating the window, in order to process WM_CREATE)
if (!process.versions.electron) {
that.hwnd = 1;
var loop = function () {
// sizeof(MSG) = 48 on 64-bit, 28 on 32-bit.
var msg = Buffer.alloc(process.arch === "x64" ? 48 : 28);
// Unable to use GetMessage because it blocks, and can't call via .async because it needs to be in the same
// thread as the window.
while (gpii.windows.user32.PeekMessageW(msg, 0, 0, 0, 1)) {
gpii.windows.user32.TranslateMessage(msg);
gpii.windows.user32.DispatchMessageW(msg);
}
if (that.hwnd) {
setTimeout(loop, 200);
}
};
loop();
}
// Create the Window.
that.hwnd = gpii.windows.user32.CreateWindowExW(
gpii.windows.API_constants.WS_EX_TOOLWINDOW,
className, className,
gpii.windows.API_constants.WS_VISIBLE,
0, 0, 200, 200,
0, 0, 0, 0);
if (!that.hwnd) {
fluid.fail(gpii.windows.win32errorText("CreateWindowEx failed", that.hwnd));
}
};
module.exports = kettle.test.bootstrap("gpii.tests.appBar.tests");