test-ui-toggle-button.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. 'use strict';
  5. module.metadata = {
  6. 'engines': {
  7. 'Firefox': '> 28'
  8. }
  9. };
  10. const { Cu } = require('chrome');
  11. const { Loader } = require('sdk/test/loader');
  12. const { data } = require('sdk/self');
  13. const { open, focus, close } = require('sdk/window/helpers');
  14. const { setTimeout } = require('sdk/timers');
  15. const { getMostRecentBrowserWindow } = require('sdk/window/utils');
  16. const { partial } = require('sdk/lang/functional');
  17. const openBrowserWindow = partial(open, null, {features: {toolbar: true}});
  18. const openPrivateBrowserWindow = partial(open, null,
  19. {features: {toolbar: true, private: true}});
  20. function getWidget(buttonId, window = getMostRecentBrowserWindow()) {
  21. const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
  22. const { AREA_NAVBAR } = CustomizableUI;
  23. let widgets = CustomizableUI.getWidgetIdsInArea(AREA_NAVBAR).
  24. filter((id) => id.startsWith('button--') && id.endsWith(buttonId));
  25. if (widgets.length === 0)
  26. throw new Error('Widget with id `' + id +'` not found.');
  27. if (widgets.length > 1)
  28. throw new Error('Unexpected number of widgets: ' + widgets.length)
  29. return CustomizableUI.getWidget(widgets[0]).forWindow(window);
  30. };
  31. exports['test basic constructor validation'] = function(assert) {
  32. let loader = Loader(module);
  33. let { ToggleButton } = loader.require('sdk/ui');
  34. assert.throws(
  35. () => ToggleButton({}),
  36. /^The option/,
  37. 'throws on no option given');
  38. // Test no label
  39. assert.throws(
  40. () => ToggleButton({ id: 'my-button', icon: './icon.png'}),
  41. /^The option "label"/,
  42. 'throws on no label given');
  43. // Test no id
  44. assert.throws(
  45. () => ToggleButton({ label: 'my button', icon: './icon.png' }),
  46. /^The option "id"/,
  47. 'throws on no id given');
  48. // Test no icon
  49. assert.throws(
  50. () => ToggleButton({ id: 'my-button', label: 'my button' }),
  51. /^The option "icon"/,
  52. 'throws on no icon given');
  53. // Test empty label
  54. assert.throws(
  55. () => ToggleButton({ id: 'my-button', label: '', icon: './icon.png' }),
  56. /^The option "label"/,
  57. 'throws on no valid label given');
  58. // Test invalid id
  59. assert.throws(
  60. () => ToggleButton({ id: 'my button', label: 'my button', icon: './icon.png' }),
  61. /^The option "id"/,
  62. 'throws on no valid id given');
  63. // Test empty id
  64. assert.throws(
  65. () => ToggleButton({ id: '', label: 'my button', icon: './icon.png' }),
  66. /^The option "id"/,
  67. 'throws on no valid id given');
  68. // Test remote icon
  69. assert.throws(
  70. () => ToggleButton({ id: 'my-button', label: 'my button', icon: 'http://www.mozilla.org/favicon.ico'}),
  71. /^The option "icon"/,
  72. 'throws on no valid icon given');
  73. // Test wrong icon: no absolute URI to local resource, neither relative './'
  74. assert.throws(
  75. () => ToggleButton({ id: 'my-button', label: 'my button', icon: 'icon.png'}),
  76. /^The option "icon"/,
  77. 'throws on no valid icon given');
  78. // Test wrong icon: no absolute URI to local resource, neither relative './'
  79. assert.throws(
  80. () => ToggleButton({ id: 'my-button', label: 'my button', icon: 'foo and bar'}),
  81. /^The option "icon"/,
  82. 'throws on no valid icon given');
  83. // Test wrong icon: '../' is not allowed
  84. assert.throws(
  85. () => ToggleButton({ id: 'my-button', label: 'my button', icon: '../icon.png'}),
  86. /^The option "icon"/,
  87. 'throws on no valid icon given');
  88. // Test wrong checked
  89. assert.throws(
  90. () => ToggleButton({
  91. id: 'my-button', label: 'my button', icon: './icon.png', checked: 'yes'}),
  92. /^The option "checked"/,
  93. 'throws on no valid checked value given');
  94. loader.unload();
  95. };
  96. exports['test button added'] = function(assert) {
  97. let loader = Loader(module);
  98. let { ToggleButton } = loader.require('sdk/ui');
  99. let button = ToggleButton({
  100. id: 'my-button-1',
  101. label: 'my button',
  102. icon: './icon.png'
  103. });
  104. // check defaults
  105. assert.equal(button.checked, false,
  106. 'checked is set to default `false` value');
  107. assert.equal(button.disabled, false,
  108. 'disabled is set to default `false` value');
  109. let { node } = getWidget(button.id);
  110. assert.ok(!!node, 'The button is in the navbar');
  111. assert.equal(button.label, node.getAttribute('label'),
  112. 'label is set');
  113. assert.equal(button.label, node.getAttribute('tooltiptext'),
  114. 'tooltip is set');
  115. assert.equal(data.url(button.icon.substr(2)), node.getAttribute('image'),
  116. 'icon is set');
  117. loader.unload();
  118. }
  119. exports['test button added with resource URI'] = function(assert) {
  120. let loader = Loader(module);
  121. let { ToggleButton } = loader.require('sdk/ui');
  122. let button = ToggleButton({
  123. id: 'my-button-1',
  124. label: 'my button',
  125. icon: data.url('icon.png')
  126. });
  127. assert.equal(button.icon, data.url('icon.png'),
  128. 'icon is set');
  129. let { node } = getWidget(button.id);
  130. assert.equal(button.icon, node.getAttribute('image'),
  131. 'icon on node is set');
  132. loader.unload();
  133. }
  134. exports['test button duplicate id'] = function(assert) {
  135. let loader = Loader(module);
  136. let { ToggleButton } = loader.require('sdk/ui');
  137. let button = ToggleButton({
  138. id: 'my-button-2',
  139. label: 'my button',
  140. icon: './icon.png'
  141. });
  142. assert.throws(() => {
  143. let doppelganger = ToggleButton({
  144. id: 'my-button-2',
  145. label: 'my button',
  146. icon: './icon.png'
  147. });
  148. },
  149. /^The ID/,
  150. 'No duplicates allowed');
  151. loader.unload();
  152. }
  153. exports['test button multiple destroy'] = function(assert) {
  154. let loader = Loader(module);
  155. let { ToggleButton } = loader.require('sdk/ui');
  156. let button = ToggleButton({
  157. id: 'my-button-2',
  158. label: 'my button',
  159. icon: './icon.png'
  160. });
  161. button.destroy();
  162. button.destroy();
  163. button.destroy();
  164. assert.pass('multiple destroy doesn\'t matter');
  165. loader.unload();
  166. }
  167. exports['test button removed on dispose'] = function(assert, done) {
  168. const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
  169. let loader = Loader(module);
  170. let { ToggleButton } = loader.require('sdk/ui');
  171. let widgetId;
  172. CustomizableUI.addListener({
  173. onWidgetDestroyed: function(id) {
  174. if (id === widgetId) {
  175. CustomizableUI.removeListener(this);
  176. assert.pass('button properly removed');
  177. loader.unload();
  178. done();
  179. }
  180. }
  181. });
  182. let button = ToggleButton({
  183. id: 'my-button-3',
  184. label: 'my button',
  185. icon: './icon.png'
  186. });
  187. // Tried to use `getWidgetIdsInArea` but seems undefined, not sure if it
  188. // was removed or it's not in the UX build yet
  189. widgetId = getWidget(button.id).id;
  190. button.destroy();
  191. };
  192. exports['test button global state updated'] = function(assert) {
  193. let loader = Loader(module);
  194. let { ToggleButton } = loader.require('sdk/ui');
  195. let button = ToggleButton({
  196. id: 'my-button-4',
  197. label: 'my button',
  198. icon: './icon.png'
  199. });
  200. // Tried to use `getWidgetIdsInArea` but seems undefined, not sure if it
  201. // was removed or it's not in the UX build yet
  202. let { node, id: widgetId } = getWidget(button.id);
  203. // check read-only properties
  204. assert.throws(() => button.id = 'another-id',
  205. /^setting a property that has only a getter/,
  206. 'id cannot be set at runtime');
  207. assert.equal(button.id, 'my-button-4',
  208. 'id is unchanged');
  209. assert.equal(node.id, widgetId,
  210. 'node id is unchanged');
  211. // check writable properties
  212. button.label = 'New label';
  213. assert.equal(button.label, 'New label',
  214. 'label is updated');
  215. assert.equal(node.getAttribute('label'), 'New label',
  216. 'node label is updated');
  217. assert.equal(node.getAttribute('tooltiptext'), 'New label',
  218. 'node tooltip is updated');
  219. button.icon = './new-icon.png';
  220. assert.equal(button.icon, './new-icon.png',
  221. 'icon is updated');
  222. assert.equal(node.getAttribute('image'), data.url('new-icon.png'),
  223. 'node image is updated');
  224. button.disabled = true;
  225. assert.equal(button.disabled, true,
  226. 'disabled is updated');
  227. assert.equal(node.getAttribute('disabled'), 'true',
  228. 'node disabled is updated');
  229. // TODO: test validation on update
  230. loader.unload();
  231. }
  232. exports['test button global state updated on multiple windows'] = function(assert, done) {
  233. let loader = Loader(module);
  234. let { ToggleButton } = loader.require('sdk/ui');
  235. let button = ToggleButton({
  236. id: 'my-button-5',
  237. label: 'my button',
  238. icon: './icon.png'
  239. });
  240. let nodes = [getWidget(button.id).node];
  241. openBrowserWindow().then(window => {
  242. nodes.push(getWidget(button.id, window).node);
  243. button.label = 'New label';
  244. button.icon = './new-icon.png';
  245. button.disabled = true;
  246. for (let node of nodes) {
  247. assert.equal(node.getAttribute('label'), 'New label',
  248. 'node label is updated');
  249. assert.equal(node.getAttribute('tooltiptext'), 'New label',
  250. 'node tooltip is updated');
  251. assert.equal(button.icon, './new-icon.png',
  252. 'icon is updated');
  253. assert.equal(node.getAttribute('image'), data.url('new-icon.png'),
  254. 'node image is updated');
  255. assert.equal(button.disabled, true,
  256. 'disabled is updated');
  257. assert.equal(node.getAttribute('disabled'), 'true',
  258. 'node disabled is updated');
  259. };
  260. return window;
  261. }).
  262. then(close).
  263. then(loader.unload).
  264. then(done, assert.fail);
  265. };
  266. exports['test button window state'] = function(assert, done) {
  267. let loader = Loader(module);
  268. let { ToggleButton } = loader.require('sdk/ui');
  269. let { browserWindows } = loader.require('sdk/windows');
  270. let button = ToggleButton({
  271. id: 'my-button-6',
  272. label: 'my button',
  273. icon: './icon.png'
  274. });
  275. let mainWindow = browserWindows.activeWindow;
  276. let nodes = [getWidget(button.id).node];
  277. openBrowserWindow().then(focus).then(window => {
  278. nodes.push(getWidget(button.id, window).node);
  279. let { activeWindow } = browserWindows;
  280. button.state(activeWindow, {
  281. label: 'New label',
  282. icon: './new-icon.png',
  283. disabled: true
  284. });
  285. // check the states
  286. assert.equal(button.label, 'my button',
  287. 'global label unchanged');
  288. assert.equal(button.icon, './icon.png',
  289. 'global icon unchanged');
  290. assert.equal(button.disabled, false,
  291. 'global disabled unchanged');
  292. let state = button.state(mainWindow);
  293. assert.equal(state.label, 'my button',
  294. 'previous window label unchanged');
  295. assert.equal(state.icon, './icon.png',
  296. 'previous window icon unchanged');
  297. assert.equal(state.disabled, false,
  298. 'previous window disabled unchanged');
  299. let state = button.state(activeWindow);
  300. assert.equal(state.label, 'New label',
  301. 'active window label updated');
  302. assert.equal(state.icon, './new-icon.png',
  303. 'active window icon updated');
  304. assert.equal(state.disabled, true,
  305. 'active disabled updated');
  306. // change the global state, only the windows without a state are affected
  307. button.label = 'A good label';
  308. assert.equal(button.label, 'A good label',
  309. 'global label updated');
  310. assert.equal(button.state(mainWindow).label, 'A good label',
  311. 'previous window label updated');
  312. assert.equal(button.state(activeWindow).label, 'New label',
  313. 'active window label unchanged');
  314. // delete the window state will inherits the global state again
  315. button.state(activeWindow, null);
  316. assert.equal(button.state(activeWindow).label, 'A good label',
  317. 'active window label inherited');
  318. // check the nodes properties
  319. let node = nodes[0];
  320. let state = button.state(mainWindow);
  321. assert.equal(node.getAttribute('label'), state.label,
  322. 'node label is correct');
  323. assert.equal(node.getAttribute('tooltiptext'), state.label,
  324. 'node tooltip is correct');
  325. assert.equal(node.getAttribute('image'), data.url(state.icon.substr(2)),
  326. 'node image is correct');
  327. assert.equal(node.hasAttribute('disabled'), state.disabled,
  328. 'disabled is correct');
  329. let node = nodes[1];
  330. let state = button.state(activeWindow);
  331. assert.equal(node.getAttribute('label'), state.label,
  332. 'node label is correct');
  333. assert.equal(node.getAttribute('tooltiptext'), state.label,
  334. 'node tooltip is correct');
  335. assert.equal(node.getAttribute('image'), data.url(state.icon.substr(2)),
  336. 'node image is correct');
  337. assert.equal(node.hasAttribute('disabled'), state.disabled,
  338. 'disabled is correct');
  339. return window;
  340. }).
  341. then(close).
  342. then(loader.unload).
  343. then(done, assert.fail);
  344. };
  345. exports['test button tab state'] = function(assert, done) {
  346. let loader = Loader(module);
  347. let { ToggleButton } = loader.require('sdk/ui');
  348. let { browserWindows } = loader.require('sdk/windows');
  349. let tabs = loader.require('sdk/tabs');
  350. let button = ToggleButton({
  351. id: 'my-button-7',
  352. label: 'my button',
  353. icon: './icon.png'
  354. });
  355. let mainTab = tabs.activeTab;
  356. let node = getWidget(button.id).node;
  357. tabs.open({
  358. url: 'about:blank',
  359. onActivate: function onActivate(tab) {
  360. tab.removeListener('activate', onActivate);
  361. let { activeWindow } = browserWindows;
  362. // set window state
  363. button.state(activeWindow, {
  364. label: 'Window label',
  365. icon: './window-icon.png'
  366. });
  367. // set previous active tab state
  368. button.state(mainTab, {
  369. label: 'Tab label',
  370. icon: './tab-icon.png',
  371. });
  372. // set current active tab state
  373. button.state(tab, {
  374. icon: './another-tab-icon.png',
  375. disabled: true
  376. });
  377. // check the states
  378. Cu.schedulePreciseGC(() => {
  379. assert.equal(button.label, 'my button',
  380. 'global label unchanged');
  381. assert.equal(button.icon, './icon.png',
  382. 'global icon unchanged');
  383. assert.equal(button.disabled, false,
  384. 'global disabled unchanged');
  385. let state = button.state(mainTab);
  386. assert.equal(state.label, 'Tab label',
  387. 'previous tab label updated');
  388. assert.equal(state.icon, './tab-icon.png',
  389. 'previous tab icon updated');
  390. assert.equal(state.disabled, false,
  391. 'previous tab disabled unchanged');
  392. let state = button.state(tab);
  393. assert.equal(state.label, 'Window label',
  394. 'active tab inherited from window state');
  395. assert.equal(state.icon, './another-tab-icon.png',
  396. 'active tab icon updated');
  397. assert.equal(state.disabled, true,
  398. 'active disabled updated');
  399. // change the global state
  400. button.icon = './good-icon.png';
  401. // delete the tab state
  402. button.state(tab, null);
  403. assert.equal(button.icon, './good-icon.png',
  404. 'global icon updated');
  405. assert.equal(button.state(mainTab).icon, './tab-icon.png',
  406. 'previous tab icon unchanged');
  407. assert.equal(button.state(tab).icon, './window-icon.png',
  408. 'tab icon inherited from window');
  409. // delete the window state
  410. button.state(activeWindow, null);
  411. assert.equal(button.state(tab).icon, './good-icon.png',
  412. 'tab icon inherited from global');
  413. // check the node properties
  414. let state = button.state(tabs.activeTab);
  415. assert.equal(node.getAttribute('label'), state.label,
  416. 'node label is correct');
  417. assert.equal(node.getAttribute('tooltiptext'), state.label,
  418. 'node tooltip is correct');
  419. assert.equal(node.getAttribute('image'), data.url(state.icon.substr(2)),
  420. 'node image is correct');
  421. assert.equal(node.hasAttribute('disabled'), state.disabled,
  422. 'disabled is correct');
  423. tabs.once('activate', () => {
  424. // This is made in order to avoid to check the node before it
  425. // is updated, need a better check
  426. setTimeout(() => {
  427. let state = button.state(mainTab);
  428. assert.equal(node.getAttribute('label'), state.label,
  429. 'node label is correct');
  430. assert.equal(node.getAttribute('tooltiptext'), state.label,
  431. 'node tooltip is correct');
  432. assert.equal(node.getAttribute('image'), data.url(state.icon.substr(2)),
  433. 'node image is correct');
  434. assert.equal(node.hasAttribute('disabled'), state.disabled,
  435. 'disabled is correct');
  436. tab.close(() => {
  437. loader.unload();
  438. done();
  439. });
  440. }, 500);
  441. });
  442. mainTab.activate();
  443. });
  444. }
  445. });
  446. };
  447. exports['test button click'] = function(assert, done) {
  448. let loader = Loader(module);
  449. let { ToggleButton } = loader.require('sdk/ui');
  450. let { browserWindows } = loader.require('sdk/windows');
  451. let labels = [];
  452. let button = ToggleButton({
  453. id: 'my-button-8',
  454. label: 'my button',
  455. icon: './icon.png',
  456. onClick: ({label}) => labels.push(label)
  457. });
  458. let mainWindow = browserWindows.activeWindow;
  459. let chromeWindow = getMostRecentBrowserWindow();
  460. openBrowserWindow().then(focus).then(window => {
  461. button.state(mainWindow, { label: 'nothing' });
  462. button.state(mainWindow.tabs.activeTab, { label: 'foo'})
  463. button.state(browserWindows.activeWindow, { label: 'bar' });
  464. button.click();
  465. focus(chromeWindow).then(() => {
  466. button.click();
  467. assert.deepEqual(labels, ['bar', 'foo'],
  468. 'button click works');
  469. close(window).
  470. then(loader.unload).
  471. then(done, assert.fail);
  472. });
  473. }).then(null, assert.fail);
  474. }
  475. exports['test button icon set'] = function(assert) {
  476. const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
  477. let loader = Loader(module);
  478. let { ToggleButton } = loader.require('sdk/ui');
  479. // Test remote icon set
  480. assert.throws(
  481. () => ToggleButton({
  482. id: 'my-button-10',
  483. label: 'my button',
  484. icon: {
  485. '16': 'http://www.mozilla.org/favicon.ico'
  486. }
  487. }),
  488. /^The option "icon"/,
  489. 'throws on no valid icon given');
  490. let button = ToggleButton({
  491. id: 'my-button-11',
  492. label: 'my button',
  493. icon: {
  494. '5': './icon5.png',
  495. '16': './icon16.png',
  496. '32': './icon32.png',
  497. '64': './icon64.png'
  498. }
  499. });
  500. let { node, id: widgetId } = getWidget(button.id);
  501. let { devicePixelRatio } = node.ownerDocument.defaultView;
  502. let size = 16 * devicePixelRatio;
  503. assert.equal(node.getAttribute('image'), data.url(button.icon[size].substr(2)),
  504. 'the icon is set properly in navbar');
  505. let size = 32 * devicePixelRatio;
  506. CustomizableUI.addWidgetToArea(widgetId, CustomizableUI.AREA_PANEL);
  507. assert.equal(node.getAttribute('image'), data.url(button.icon[size].substr(2)),
  508. 'the icon is set properly in panel');
  509. // Using `loader.unload` without move back the button to the original area
  510. // raises an error in the CustomizableUI. This is doesn't happen if the
  511. // button is moved manually from navbar to panel. I believe it has to do
  512. // with `addWidgetToArea` method, because even with a `timeout` the issue
  513. // persist.
  514. CustomizableUI.addWidgetToArea(widgetId, CustomizableUI.AREA_NAVBAR);
  515. loader.unload();
  516. }
  517. exports['test button icon se with only one option'] = function(assert) {
  518. const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
  519. let loader = Loader(module);
  520. let { ToggleButton } = loader.require('sdk/ui');
  521. // Test remote icon set
  522. assert.throws(
  523. () => ToggleButton({
  524. id: 'my-button-10',
  525. label: 'my button',
  526. icon: {
  527. '16': 'http://www.mozilla.org/favicon.ico'
  528. }
  529. }),
  530. /^The option "icon"/,
  531. 'throws on no valid icon given');
  532. let button = ToggleButton({
  533. id: 'my-button-11',
  534. label: 'my button',
  535. icon: {
  536. '5': './icon5.png'
  537. }
  538. });
  539. let { node, id: widgetId } = getWidget(button.id);
  540. assert.equal(node.getAttribute('image'), data.url(button.icon['5'].substr(2)),
  541. 'the icon is set properly in navbar');
  542. CustomizableUI.addWidgetToArea(widgetId, CustomizableUI.AREA_PANEL);
  543. assert.equal(node.getAttribute('image'), data.url(button.icon['5'].substr(2)),
  544. 'the icon is set properly in panel');
  545. // Using `loader.unload` without move back the button to the original area
  546. // raises an error in the CustomizableUI. This is doesn't happen if the
  547. // button is moved manually from navbar to panel. I believe it has to do
  548. // with `addWidgetToArea` method, because even with a `timeout` the issue
  549. // persist.
  550. CustomizableUI.addWidgetToArea(widgetId, CustomizableUI.AREA_NAVBAR);
  551. loader.unload();
  552. }
  553. exports['test button state validation'] = function(assert) {
  554. let loader = Loader(module);
  555. let { ToggleButton } = loader.require('sdk/ui');
  556. let { browserWindows } = loader.require('sdk/windows');
  557. let button = ToggleButton({
  558. id: 'my-button-12',
  559. label: 'my button',
  560. icon: './icon.png'
  561. })
  562. let state = button.state(button);
  563. assert.throws(
  564. () => button.state(button, { icon: 'http://www.mozilla.org/favicon.ico' }),
  565. /^The option "icon"/,
  566. 'throws on remote icon given');
  567. loader.unload();
  568. };
  569. exports['test button are not in private windows'] = function(assert, done) {
  570. let loader = Loader(module);
  571. let { ToggleButton } = loader.require('sdk/ui');
  572. let{ isPrivate } = loader.require('sdk/private-browsing');
  573. let { browserWindows } = loader.require('sdk/windows');
  574. let button = ToggleButton({
  575. id: 'my-button-13',
  576. label: 'my button',
  577. icon: './icon.png'
  578. });
  579. openPrivateBrowserWindow().then(window => {
  580. assert.ok(isPrivate(window),
  581. 'the new window is private');
  582. let { node } = getWidget(button.id, window);
  583. assert.ok(!node || node.style.display === 'none',
  584. 'the button is not added / is not visible on private window');
  585. return window;
  586. }).
  587. then(close).
  588. then(loader.unload).
  589. then(done, assert.fail)
  590. }
  591. exports['test button state are snapshot'] = function(assert) {
  592. let loader = Loader(module);
  593. let { ToggleButton } = loader.require('sdk/ui');
  594. let { browserWindows } = loader.require('sdk/windows');
  595. let tabs = loader.require('sdk/tabs');
  596. let button = ToggleButton({
  597. id: 'my-button-14',
  598. label: 'my button',
  599. icon: './icon.png'
  600. });
  601. let state = button.state(button);
  602. let windowState = button.state(browserWindows.activeWindow);
  603. let tabState = button.state(tabs.activeTab);
  604. assert.deepEqual(windowState, state,
  605. 'window state has the same properties of button state');
  606. assert.deepEqual(tabState, state,
  607. 'tab state has the same properties of button state');
  608. assert.notEqual(windowState, state,
  609. 'window state is not the same object of button state');
  610. assert.notEqual(tabState, state,
  611. 'tab state is not the same object of button state');
  612. assert.deepEqual(button.state(button), state,
  613. 'button state has the same content of previous button state');
  614. assert.deepEqual(button.state(browserWindows.activeWindow), windowState,
  615. 'window state has the same content of previous window state');
  616. assert.deepEqual(button.state(tabs.activeTab), tabState,
  617. 'tab state has the same content of previous tab state');
  618. assert.notEqual(button.state(button), state,
  619. 'button state is not the same object of previous button state');
  620. assert.notEqual(button.state(browserWindows.activeWindow), windowState,
  621. 'window state is not the same object of previous window state');
  622. assert.notEqual(button.state(tabs.activeTab), tabState,
  623. 'tab state is not the same object of previous tab state');
  624. loader.unload();
  625. }
  626. exports['test button after destroy'] = function(assert) {
  627. let loader = Loader(module);
  628. let { ToggleButton } = loader.require('sdk/ui');
  629. let { browserWindows } = loader.require('sdk/windows');
  630. let { activeTab } = loader.require('sdk/tabs');
  631. let button = ToggleButton({
  632. id: 'my-button-15',
  633. label: 'my button',
  634. icon: './icon.png',
  635. onClick: () => assert.fail('onClick should not be called')
  636. });
  637. button.destroy();
  638. assert.throws(
  639. () => button.click(),
  640. /^The state cannot be set or get/,
  641. 'button.click() not executed');
  642. assert.throws(
  643. () => button.label,
  644. /^The state cannot be set or get/,
  645. 'button.label cannot be get after destroy');
  646. assert.throws(
  647. () => button.label = 'my label',
  648. /^The state cannot be set or get/,
  649. 'button.label cannot be set after destroy');
  650. assert.throws(
  651. () => {
  652. button.state(browserWindows.activeWindow, {
  653. label: 'window label'
  654. });
  655. },
  656. /^The state cannot be set or get/,
  657. 'window state label cannot be set after destroy');
  658. assert.throws(
  659. () => button.state(browserWindows.activeWindow).label,
  660. /^The state cannot be set or get/,
  661. 'window state label cannot be get after destroy');
  662. assert.throws(
  663. () => {
  664. button.state(activeTab, {
  665. label: 'tab label'
  666. });
  667. },
  668. /^The state cannot be set or get/,
  669. 'tab state label cannot be set after destroy');
  670. assert.throws(
  671. () => button.state(activeTab).label,
  672. /^The state cannot be set or get/,
  673. 'window state label cannot se get after destroy');
  674. loader.unload();
  675. };
  676. exports['test button checked'] = function(assert, done) {
  677. let loader = Loader(module);
  678. let { ToggleButton } = loader.require('sdk/ui');
  679. let { browserWindows } = loader.require('sdk/windows');
  680. let events = [];
  681. let button = ToggleButton({
  682. id: 'my-button-9',
  683. label: 'my button',
  684. icon: './icon.png',
  685. checked: true,
  686. onClick: ({label}) => events.push('clicked:' + label),
  687. onChange: state => events.push('changed:' + state.label + ':' + state.checked)
  688. });
  689. let { node } = getWidget(button.id);
  690. assert.equal(node.getAttribute('type'), 'checkbox',
  691. 'node type is properly set');
  692. let mainWindow = browserWindows.activeWindow;
  693. let chromeWindow = getMostRecentBrowserWindow();
  694. openBrowserWindow().then(focus).then(window => {
  695. button.state(mainWindow, { label: 'nothing' });
  696. button.state(mainWindow.tabs.activeTab, { label: 'foo'})
  697. button.state(browserWindows.activeWindow, { label: 'bar' });
  698. button.click();
  699. button.click();
  700. focus(chromeWindow).then(() => {
  701. button.click();
  702. button.click();
  703. assert.deepEqual(events, [
  704. 'clicked:bar', 'changed:bar:false', 'clicked:bar', 'changed:bar:true',
  705. 'clicked:foo', 'changed:foo:false', 'clicked:foo', 'changed:foo:true'
  706. ],
  707. 'button change events works');
  708. close(window).
  709. then(loader.unload).
  710. then(done, assert.fail);
  711. })
  712. }).then(null, assert.fail);
  713. }
  714. exports['test button is checked on window level'] = function(assert, done) {
  715. let loader = Loader(module);
  716. let { ToggleButton } = loader.require('sdk/ui');
  717. let { browserWindows } = loader.require('sdk/windows');
  718. let tabs = loader.require('sdk/tabs');
  719. let button = ToggleButton({
  720. id: 'my-button-20',
  721. label: 'my button',
  722. icon: './icon.png'
  723. });
  724. let mainWindow = browserWindows.activeWindow;
  725. let mainTab = tabs.activeTab;
  726. assert.equal(button.checked, false,
  727. 'global state, checked is `false`.');
  728. assert.equal(button.state(mainTab).checked, false,
  729. 'tab state, checked is `false`.');
  730. assert.equal(button.state(mainWindow).checked, false,
  731. 'window state, checked is `false`.');
  732. button.click();
  733. tabs.open({
  734. url: 'about:blank',
  735. onActivate: function onActivate(tab) {
  736. tab.removeListener('activate', onActivate);
  737. assert.notEqual(mainTab, tab,
  738. 'the current tab is not the same.');
  739. assert.equal(button.checked, false,
  740. 'global state, checked is `false`.');
  741. assert.equal(button.state(mainTab).checked, true,
  742. 'previous tab state, checked is `true`.');
  743. assert.equal(button.state(tab).checked, true,
  744. 'current tab state, checked is `true`.');
  745. assert.equal(button.state(mainWindow).checked, true,
  746. 'window state, checked is `true`.');
  747. openBrowserWindow().then(focus).then(window => {
  748. let { activeWindow } = browserWindows;
  749. let { activeTab } = activeWindow.tabs;
  750. assert.equal(button.checked, false,
  751. 'global state, checked is `false`.');
  752. assert.equal(button.state(activeTab).checked, false,
  753. 'tab state, checked is `false`.');
  754. assert.equal(button.state(activeWindow).checked, false,
  755. 'window state, checked is `false`.');
  756. tab.close(()=> {
  757. close(window).
  758. then(loader.unload).
  759. then(done, assert.fail);
  760. })
  761. }).
  762. then(null, assert.fail);
  763. }
  764. });
  765. };
  766. exports['test button click do not messing up states'] = function(assert) {
  767. let loader = Loader(module);
  768. let { ToggleButton } = loader.require('sdk/ui');
  769. let { browserWindows } = loader.require('sdk/windows');
  770. let button = ToggleButton({
  771. id: 'my-button-21',
  772. label: 'my button',
  773. icon: './icon.png'
  774. });
  775. let mainWindow = browserWindows.activeWindow;
  776. let { activeTab } = mainWindow.tabs;
  777. button.state(mainWindow, { icon: './new-icon.png' });
  778. button.state(activeTab, { label: 'foo'})
  779. assert.equal(button.state(mainWindow).label, 'my button',
  780. 'label property for window state, properly derived from global state');
  781. assert.equal(button.state(activeTab).icon, './new-icon.png',
  782. 'icon property for tab state, properly derived from window state');
  783. button.click();
  784. button.label = 'bar';
  785. assert.equal(button.state(mainWindow).label, 'bar',
  786. 'label property for window state, properly derived from global state');
  787. button.state(mainWindow, null);
  788. assert.equal(button.state(activeTab).icon, './icon.png',
  789. 'icon property for tab state, properly derived from window state');
  790. loader.unload();
  791. }
  792. // If the module doesn't support the app we're being run in, require() will
  793. // throw. In that case, remove all tests above from exports, and add one dummy
  794. // test that passes.
  795. try {
  796. require('sdk/ui/button/toggle');
  797. }
  798. catch (err) {
  799. if (!/^Unsupported Application/.test(err.message))
  800. throw err;
  801. module.exports = {
  802. 'test Unsupported Application': assert => assert.pass(err.message)
  803. }
  804. }
  805. require('sdk/test').run(exports);