/** * Copyright (c) HashiCorp, Inc. * SPDX-License-Identifier: BUSL-1.1 */ import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import { render, focus, triggerKeyEvent, typeIn, fillIn, click } from '@ember/test-helpers'; import { GENERAL } from 'vault/tests/helpers/general-selectors'; import hbs from 'htmlbars-inline-precompile'; import sinon from 'sinon'; const SELECTORS = { stringify: '[data-test-stringify-toggle]', }; module('Integration | Component | masked input', function (hooks) { setupRenderingTest(hooks); hooks.beforeEach(function () { this.flashMessages = this.owner.lookup('service:flash-messages'); this.flashMessages.registerTypes(['success', 'danger']); this.flashSuccessSpy = sinon.spy(this.flashMessages, 'success'); this.flashDangerSpy = sinon.spy(this.flashMessages, 'danger'); }); test('it renders', async function (assert) { await render(hbs``); assert.dom('[data-test-masked-input]').exists('shows masked input'); assert.dom('textarea').exists(); assert.dom('textarea').hasClass('masked-font', 'it renders an input with obscure font'); assert.dom(GENERAL.copyButton).doesNotExist('does not render copy button by default'); assert.dom(GENERAL.button('Download')).doesNotExist('does not render download button by default'); await click(GENERAL.button('toggle-masked')); assert.dom('.masked-value').doesNotHaveClass('masked-font', 'it unmasks when show button is clicked'); await click(GENERAL.button('toggle-masked')); assert.dom('.masked-value').hasClass('masked-font', 'it remasks text when button is clicked'); }); test('it renders correctly when displayOnly', async function (assert) { this.set('value', 'value'); await render(hbs``); assert.dom('.masked-value').hasClass('masked-font', 'value has obscured font'); assert.dom('textarea').doesNotExist('it does not render a textarea when displayOnly is true'); }); test('it renders a copy button when allowCopy is true', async function (assert) { this.set('value', { some: 'object' }); await render(hbs``); assert.dom(GENERAL.copyButton).exists(); }); test('it renders a download button when allowDownload is true', async function (assert) { await render(hbs` `); assert.dom(GENERAL.button('Download secret value')).exists(); await click(GENERAL.button('Download secret value')); assert.dom(GENERAL.button('Download')).exists('clicking download icon opens modal with download button'); }); test('it shortens all outputs when displayOnly and masked', async function (assert) { this.set('value', '123456789-123456789-123456789'); await render(hbs``); const maskedValue = document.querySelector('.masked-value').innerText; assert.strictEqual(maskedValue.length, 11); await click(GENERAL.button('toggle-masked')); const unMaskedValue = document.querySelector('.masked-value').innerText; assert.strictEqual(unMaskedValue.length, this.value.length); }); test('it does not unmask text on focus', async function (assert) { this.set('value', '123456789-123456789-123456789'); await render(hbs``); assert.dom('.masked-value').hasClass('masked-font'); await focus('.masked-value'); assert.dom('.masked-value').hasClass('masked-font'); }); test('it calls onChange events with the correct values', async function (assert) { const changeSpy = sinon.spy(); this.set('value', 'before'); this.set('onChange', changeSpy); await render(hbs``); await fillIn('textarea', 'after'); assert.true(changeSpy.calledWith('after')); }); test('it calls onKeyUp events with the correct values', async function (assert) { const keyupSpy = sinon.spy(); this.set('value', ''); this.set('onKeyUp', keyupSpy); await render(hbs``); await typeIn('textarea', 'baz'); assert.true(keyupSpy.calledThrice, 'calls for each letter of typing'); assert.true(keyupSpy.firstCall.calledWithExactly('foo', 'b')); assert.true(keyupSpy.secondCall.calledWithExactly('foo', 'ba')); assert.true(keyupSpy.thirdCall.calledWithExactly('foo', 'baz')); }); test('it does not remove value on tab', async function (assert) { this.set('value', 'hello'); await render(hbs``); await triggerKeyEvent('textarea', 'keydown', 9); await click(GENERAL.button('toggle-masked')); const unMaskedValue = document.querySelector('.masked-value').value; assert.strictEqual(unMaskedValue, this.value); }); test('it renders a minus icon when an empty string is provided as a value', async function (assert) { await render(hbs` `); assert.dom('[data-test-masked-input]').exists('shows masked input'); assert.dom(GENERAL.copyButton).exists(); assert.dom(GENERAL.button('Download secret value')).exists(); assert.dom(GENERAL.button('toggle-masked')).exists('shows toggle mask button'); await click(GENERAL.button('toggle-masked')); assert.dom('.masked-value').doesNotHaveClass('masked-font', 'it unmasks when show button is clicked'); assert .dom('[data-test-icon="minus"]') .exists('shows minus icon when unmasked because value is empty string'); }); test('it should render stringify toggle in download modal', async function (assert) { assert.expect(3); // this looks wonky but need a new line in there to test stringify adding escape character this.value = `bar `; const downloadStub = sinon.stub(this.owner.lookup('service:download'), 'miscExtension'); downloadStub.callsFake((fileName, value) => { const firstCall = downloadStub.callCount === 1; const assertVal = firstCall ? this.value : JSON.stringify(this.value); assert.strictEqual(assertVal, value, `Value is ${firstCall ? 'not ' : ''}stringified`); return true; }); await render(hbs` `); await click(GENERAL.button('Download secret value')); assert.dom(SELECTORS.stringify).isNotChecked('Stringify toggle off as default'); await click(GENERAL.button('Download')); await click(GENERAL.button('Download secret value')); await click(SELECTORS.stringify); await click(GENERAL.button('Download')); }); });