// state change events. That gets extra confusing for CapsLock
// which toggles on each press, but not on release. So pretend
// it was a quick press and release of the button.
- if (browser.isMac() && (code === 'CapsLock')) {
+ if ((browser.isMac() || browser.isIOS()) && (code === 'CapsLock')) {
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false);
stopEvent(e);
}
// See comment in _handleKeyDown()
- if (browser.isMac() && (code === 'CapsLock')) {
+ if ((browser.isMac() || browser.isIOS()) && (code === 'CapsLock')) {
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false);
return;
});
});
+ describe('Caps Lock on iOS and macOS', function () {
+ let origNavigator;
+ beforeEach(function () {
+ // window.navigator is a protected read-only property in many
+ // environments, so we need to redefine it whilst running these
+ // tests.
+ origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
+ if (origNavigator === undefined) {
+ // Object.getOwnPropertyDescriptor() doesn't work
+ // properly in any version of IE
+ this.skip();
+ }
+
+ Object.defineProperty(window, "navigator", {value: {}});
+ if (window.navigator.platform !== undefined) {
+ // Object.defineProperty() doesn't work properly in old
+ // versions of Chrome
+ this.skip();
+ }
+ });
+
+ afterEach(function () {
+ Object.defineProperty(window, "navigator", origNavigator);
+ });
+
+ it('should toggle caps lock on key press on iOS', function (done) {
+ window.navigator.platform = "iPad";
+ const kbd = new Keyboard(document);
+ kbd.onkeyevent = sinon.spy();
+ kbd._handleKeyDown(keyevent('keydown', {code: 'CapsLock', key: 'CapsLock'}));
+
+ expect(kbd.onkeyevent).to.have.been.calledTwice;
+ expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true);
+ expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false);
+ done();
+ });
+
+ it('should toggle caps lock on key press on mac', function (done) {
+ window.navigator.platform = "Mac";
+ const kbd = new Keyboard(document);
+ kbd.onkeyevent = sinon.spy();
+ kbd._handleKeyDown(keyevent('keydown', {code: 'CapsLock', key: 'CapsLock'}));
+
+ expect(kbd.onkeyevent).to.have.been.calledTwice;
+ expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true);
+ expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false);
+ done();
+ });
+
+ it('should toggle caps lock on key release on iOS', function (done) {
+ window.navigator.platform = "iPad";
+ const kbd = new Keyboard(document);
+ kbd.onkeyevent = sinon.spy();
+ kbd._handleKeyUp(keyevent('keyup', {code: 'CapsLock', key: 'CapsLock'}));
+
+ expect(kbd.onkeyevent).to.have.been.calledTwice;
+ expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true);
+ expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false);
+ done();
+ });
+
+ it('should toggle caps lock on key release on mac', function (done) {
+ window.navigator.platform = "Mac";
+ const kbd = new Keyboard(document);
+ kbd.onkeyevent = sinon.spy();
+ kbd._handleKeyUp(keyevent('keyup', {code: 'CapsLock', key: 'CapsLock'}));
+
+ expect(kbd.onkeyevent).to.have.been.calledTwice;
+ expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true);
+ expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false);
+ done();
+ });
+ });
+
describe('Escape AltGraph on Windows', function () {
let origNavigator;
beforeEach(function () {
// skip these -- they don't belong in the processed application
path.join(paths.vendor, 'sinon.js'),
path.join(paths.vendor, 'browser-es-module-loader'),
- path.join(paths.vendor, 'promise.js'),
path.join(paths.app, 'images', 'icons', 'Makefile'),
]);
+const only_legacy_scripts = new Set([
+ path.join(paths.vendor, 'promise.js'),
+]);
+
const no_transform_files = new Set([
// don't transform this -- we want it imported as-is to properly catch loading errors
path.join(paths.app, 'error-handler.js'),
// Otherwise include both modules and legacy fallbacks
new_script += ' <script type="module" crossorigin="anonymous" src="app/ui.js"></script>\n';
for (let i = 0;i < legacy_scripts.length;i++) {
- new_script += ' <script nomodule src="${legacy_scripts[i]}"></script>\n';
+ new_script += ` <script nomodule src="${legacy_scripts[i]}"></script>\n`;
}
}
const helper = helpers[import_format];
const outFiles = [];
+ const legacyFiles = [];
const handleDir = (js_only, vendor_rewrite, in_path_base, filename) => Promise.resolve()
.then(() => {
- if (no_copy_files.has(filename)) return;
-
const out_path = path.join(out_path_base, path.relative(in_path_base, filename));
const legacy_path = path.join(legacy_path_base, path.relative(in_path_base, filename));
return; // skip non-javascript files
}
+ if (no_transform_files.has(filename)) {
+ return ensureDir(path.dirname(out_path))
+ .then(() => {
+ console.log(`Writing ${out_path}`);
+ return copy(filename, out_path);
+ });
+ }
+
+ if (only_legacy_scripts.has(filename)) {
+ legacyFiles.push(legacy_path);
+ return ensureDir(path.dirname(legacy_path))
+ .then(() => {
+ console.log(`Writing ${legacy_path}`);
+ return copy(filename, legacy_path);
+ });
+ }
+
return Promise.resolve()
.then(() => {
- if (only_legacy && !no_transform_files.has(filename)) {
+ if (only_legacy) {
return;
}
return ensureDir(path.dirname(out_path))
})
.then(() => ensureDir(path.dirname(legacy_path)))
.then(() => {
- if (no_transform_files.has(filename)) {
- return;
- }
-
const opts = babel_opts();
if (helper && helpers.optionsOverride) {
helper.optionsOverride(opts);
});
});
- if (with_app_dir && helper && helper.noCopyOverride) {
- helper.noCopyOverride(paths, no_copy_files);
- }
-
Promise.resolve()
.then(() => {
const handler = handleDir.bind(null, true, false, in_path || paths.main);
console.log(`Writing ${out_app_path}`);
return helper.appWriter(out_path_base, legacy_path_base, out_app_path)
.then((extra_scripts) => {
- const rel_app_path = path.relative(out_path_base, out_app_path);
- const legacy_scripts = extra_scripts.concat([rel_app_path]);
+ let legacy_scripts = extra_scripts;
+
+ legacyFiles.forEach((file) => {
+ let rel_file_path = path.relative(out_path_base, file);
+ legacy_scripts.push(rel_file_path);
+ });
+
+ let rel_app_path = path.relative(out_path_base, out_app_path);
+ legacy_scripts.push(rel_app_path);
+
transform_html(legacy_scripts, only_legacy);
})
.then(() => {
return [ require_path ];
});
},
- noCopyOverride: () => {},
},
'commonjs': {
optionsOverride: (opts) => {
.then(buf => writeFile(out_path, buf))
.then(() => []);
},
- noCopyOverride: () => {},
removeModules: true,
},
'systemjs': {
return writeFile(out_path, `SystemJS.import("${ui_path}");`)
.then(() => {
console.log(`Please place SystemJS in ${path.join(script_base_path, 'system-production.js')}`);
- // FIXME: Should probably be in the legacy directory
- const promise_path = path.relative(base_out_path,
- path.join(base_out_path, 'vendor', 'promise.js'));
const systemjs_path = path.relative(base_out_path,
path.join(script_base_path, 'system-production.js'));
- return [ promise_path, systemjs_path ];
+ return [ systemjs_path ];
});
},
- noCopyOverride: (paths, no_copy_files) => {
- no_copy_files.delete(path.join(paths.vendor, 'promise.js'));
- },
},
'umd': {
optionsOverride: (opts) => {