import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import { click, fillIn, find, render, waitUntil } from '@ember/test-helpers'; import hbs from 'htmlbars-inline-precompile'; import Layer2TestWeb3Strategy from '@cardstack/web-client/utils/web3-strategies/test-layer2'; import { WorkflowSession } from '@cardstack/web-client/models/workflow'; import { setupMirage } from 'ember-cli-mirage/test-support'; import { MirageTestContext } from 'ember-cli-mirage/test-support'; import { Response as MirageResponse } from 'ember-cli-mirage'; import { createPrepaidCardSafe } from '@cardstack/web-client/utils/test-factories'; interface Context extends MirageTestContext {} // selectors let PREVIEW = '[data-test-merchant-customization-merchant-preview]'; let MERCHANT = '[data-test-merchant]'; let MERCHANT_LOGO = '[data-test-merchant-logo]'; let MERCHANT_NAME_FIELD = '[data-test-merchant-customization-merchant-name-field]'; let MERCHANT_ID_FIELD = '[data-test-merchant-customization-merchant-id-field]'; let COLOR_FIELD = '[data-test-merchant-customization-color-field]'; let MANAGER = '[data-test-merchant-customization-manager-address]'; let SAVE_DETAILS_BUTTON = '[data-test-merchant-customization-save-details]'; let EDIT_BUTTON = '[data-test-merchant-customization-edit]'; let COMPLETED_SELECTOR = '[data-test-merchant-customization-is-complete]'; // fixtures for validation const MERCHANT_NAME_INVALID_INPUTS = [ { value: '', errorMessage: 'This field is required', }, { value: ' ', errorMessage: 'This field is required', }, ]; const VALID_ID = 'abc123'; const MERCHANT_ID_INVALID_INPUTS = [ { value: '', errorMessage: 'This field is required', }, { value: ' ', errorMessage: 'This field is required', }, { value: 'an invalid id because of spaces', errorMessage: 'The Business ID can only contain lowercase letters or numbers, no special characters', }, { value: 'INVALIDCASING', errorMessage: 'The Business ID can only contain lowercase letters or numbers, no special characters', }, { value: '😤', errorMessage: 'The Business ID can only contain lowercase letters or numbers, no special characters', }, { value: 'thisisexactlyfiftyfivecharacterslongbutisotherwisevalid', errorMessage: 'The Business ID cannot be more than 50 characters long. It is currently 55 characters long', }, ]; let layer2AccountAddress = '0x182619c6Ea074C053eF3f1e1eF81Ec8De6Eb6E44'; module( 'Integration | Component | card-pay/create-merchant/merchant-customization', function (hooks) { let layer2Service: Layer2TestWeb3Strategy; let workflowSession: WorkflowSession; setupRenderingTest(hooks); setupMirage(hooks); hooks.beforeEach(async function () { layer2Service = this.owner.lookup('service:layer2-network') .strategy as Layer2TestWeb3Strategy; workflowSession = new WorkflowSession(); let prepaidCardAddress = '0x123400000000000000000000000000000000abcd'; layer2Service.test__simulateRemoteAccountSafes(layer2AccountAddress, [ createPrepaidCardSafe({ address: prepaidCardAddress, owners: [layer2AccountAddress], prepaidCardOwner: layer2AccountAddress, issuer: layer2AccountAddress, spendFaceValue: 2324, reloadable: false, transferrable: false, }), ]); await layer2Service.test__simulateAccountsChanged([layer2AccountAddress]); this.setProperties({ onComplete: () => { this.set('isComplete', true); }, onIncomplete: () => { this.set('isComplete', false); }, isComplete: false, frozen: false, workflowSession, }); await render(hbs` `); }); test('It displays the default state correctly', async function (assert) { assert.dom(PREVIEW).exists(); assert.dom(`${PREVIEW} ${MERCHANT}`).containsText('Enter business name'); assert .dom(`${PREVIEW} ${MERCHANT}`) .hasAttribute('data-test-merchant', 'Enter business name'); assert.dom(MERCHANT_NAME_FIELD).exists(); assert.dom(MERCHANT_ID_FIELD).exists(); assert.dom(COLOR_FIELD).exists(); assert.dom(SAVE_DETAILS_BUTTON).exists().isDisabled(); assert.dom(MANAGER).containsText(layer2AccountAddress); }); test('It validates the merchant name field and updates the preview', async function (assert) { // enter valid merchant id so we can check that the merchant name being valid will make // the save details button enabled let merchantIdInput = `${MERCHANT_ID_FIELD} input`; await fillIn(merchantIdInput, VALID_ID); await waitUntil( () => (find('[data-test-validation-state-input]') as HTMLElement).dataset .testValidationStateInput === 'valid' ); assert.dom(SAVE_DETAILS_BUTTON).isDisabled(); let merchantNameInput = `${MERCHANT_NAME_FIELD} input`; // valid await fillIn(merchantNameInput, 'HELLO!'); assert.dom(merchantNameInput).hasValue('HELLO!'); assert .dom('[data-test-merchant]') .hasAttribute('data-test-merchant', 'HELLO!') .containsText('HELLO!'); assert.dom(SAVE_DETAILS_BUTTON).isEnabled(); for (let invalidEntry of MERCHANT_NAME_INVALID_INPUTS) { await fillIn(merchantNameInput, invalidEntry.value); assert .dom(`${MERCHANT_NAME_FIELD} [data-test-boxel-input-error-message]`) .containsText(invalidEntry.errorMessage); assert.dom(SAVE_DETAILS_BUTTON).isDisabled(); } }); test('It validates the merchant ID field', async function (assert) { let merchantNameInput = `${MERCHANT_NAME_FIELD} input`; await fillIn(merchantNameInput, 'HELLO!'); assert.dom(merchantNameInput).hasValue('HELLO!'); assert .dom('[data-test-merchant]') .hasAttribute('data-test-merchant', 'HELLO!') .containsText('HELLO!'); assert.dom(SAVE_DETAILS_BUTTON).isDisabled(); let merchantIdInput = `${MERCHANT_ID_FIELD} input`; await fillIn(merchantIdInput, VALID_ID); await waitUntil( () => (find('[data-test-validation-state-input]') as HTMLElement).dataset .testValidationStateInput === 'valid' ); assert.dom(SAVE_DETAILS_BUTTON).isEnabled(); for (let invalidEntry of MERCHANT_ID_INVALID_INPUTS) { await fillIn(merchantIdInput, invalidEntry.value); assert .dom(`${MERCHANT_ID_FIELD} [data-test-boxel-input-error-message]`) .containsText(invalidEntry.errorMessage); assert.dom(SAVE_DETAILS_BUTTON).isDisabled(); } }); test('It validates a given id and displays the error message returned from the hub', async function (this: Context, assert) { this.server.create('merchant-info', { slug: 'existing' }); await fillIn(`${MERCHANT_NAME_FIELD} input`, 'HELLO!'); let merchantIdInput = `${MERCHANT_ID_FIELD} input`; await fillIn(merchantIdInput, 'existing'); await waitUntil( () => (find('[data-test-validation-state-input]') as HTMLElement).dataset .testValidationStateInput === 'invalid' ); assert .dom(`${MERCHANT_ID_FIELD} [data-test-boxel-input-error-message]`) .containsText( 'This Business ID is already taken. Please choose another one' ); assert.dom(SAVE_DETAILS_BUTTON).isDisabled(); await fillIn(merchantIdInput, 'unique'); await waitUntil( () => (find('[data-test-validation-state-input]') as HTMLElement).dataset .testValidationStateInput === 'valid' ); assert.dom(SAVE_DETAILS_BUTTON).isEnabled(); // www is hardcoded to be forbidden in mirage await fillIn(merchantIdInput, 'www'); await waitUntil( () => (find('[data-test-validation-state-input]') as HTMLElement).dataset .testValidationStateInput === 'invalid' ); assert .dom(`${MERCHANT_ID_FIELD} [data-test-boxel-input-error-message]`) .containsText('This Business ID is not allowed'); assert.dom(SAVE_DETAILS_BUTTON).isDisabled(); }); test('It shows when there is an error validating id uniqueness', async function (this: Context, assert) { this.server.get('/merchant-infos/validate-slug/:slug', function () { return new MirageResponse(500, {}, ''); }); await fillIn(`${MERCHANT_NAME_FIELD} input`, 'HELLO!'); let merchantIdInput = `${MERCHANT_ID_FIELD} input`; await fillIn(merchantIdInput, 'existing'); await waitUntil( () => (find('[data-test-validation-state-input]') as HTMLElement).dataset .testValidationStateInput === 'invalid' ); assert .dom(`${MERCHANT_ID_FIELD} [data-test-boxel-input-error-message]`) .containsText('There was an error validating business ID uniqueness'); assert.dom(SAVE_DETAILS_BUTTON).isDisabled(); }); test('It updates the workflow session when saved', async function (assert) { assert.notOk(workflowSession.getValue('merchantName')); assert.notOk(workflowSession.getValue('merchantId')); assert.notOk(workflowSession.getValue('merchantBgColor')); assert.notOk(workflowSession.getValue('merchantTextColor')); let merchantNameInput = `${MERCHANT_NAME_FIELD} input`; await fillIn(merchantNameInput, 'HELLO!'); let merchantIdInput = `${MERCHANT_ID_FIELD} input`; await fillIn(merchantIdInput, VALID_ID); await waitUntil( () => (find('[data-test-validation-state-input]') as HTMLElement).dataset .testValidationStateInput === 'valid' ); await click(SAVE_DETAILS_BUTTON); assert.equal(workflowSession.getValue('merchantName'), 'HELLO!'); assert.equal(workflowSession.getValue('merchantId'), VALID_ID); assert.ok(workflowSession.getValue('merchantBgColor')); assert.ok(workflowSession.getValue('merchantTextColor')); }); test('It displays the memorialized state correctly', async function (assert) { let merchantNameInput = `${MERCHANT_NAME_FIELD} input`; await fillIn(merchantNameInput, 'HELLO!'); let merchantIdInput = `${MERCHANT_ID_FIELD} input`; await fillIn(merchantIdInput, VALID_ID); await waitUntil( () => (find('[data-test-validation-state-input]') as HTMLElement).dataset .testValidationStateInput === 'valid' ); await click(SAVE_DETAILS_BUTTON); let bgColor = workflowSession.getValue('merchantBgColor')!; let textColor = workflowSession.getValue('merchantTextColor')!; assert.dom(COMPLETED_SELECTOR).exists(); assert.dom(EDIT_BUTTON).exists(); assert.dom(`${PREVIEW} ${MERCHANT}`).containsText('HELLO!'); assert .dom(`${PREVIEW} ${MERCHANT_LOGO}`) .hasAttribute('data-test-merchant-logo-background', bgColor); assert .dom(`${PREVIEW} ${MERCHANT_LOGO}`) .hasAttribute('data-test-merchant-logo-text-color', textColor); assert .dom(`${PREVIEW} ${MERCHANT}`) .hasAttribute('data-test-merchant', 'HELLO!'); assert.dom(MERCHANT_NAME_FIELD).doesNotExist(); assert.dom(MERCHANT_ID_FIELD).containsText(VALID_ID); assert.dom(COLOR_FIELD).containsText(bgColor); assert.dom(MANAGER).containsText(layer2AccountAddress); }); } );