chore: 🤖 migrate host catalog and host set list wrapper fields to key value (#3276)

This commit is contained in:
Dharini committed 2026-06-03 16:36:29 -07:00
1 parent 4e2b779d96
commit 0e223ded8f
26 files changed
+512 -388

No files matched your search

@@ -97,9 +97,7 @@ module('Acceptance | host-catalogs | host sets | create', function (hooks) {
const name = 'aws host set';
await fillIn(commonSelectors.FIELD_NAME, name);
await fillIn(selectors.FIELD_PREFERRED_ENDPOINT, 'endpoint');
await click(selectors.FIELD_PREFERRED_ENDPOINT_ADD_BTN);
await fillIn(selectors.FIELD_FILTERS, 'filter_test');
await click(selectors.FIELD_FILTERS_ADD_BTN);
await fillIn(selectors.FIELD_SYNC_INTERVAL, 10);
await click(commonSelectors.SAVE_BTN);
@@ -128,7 +126,6 @@ module('Acceptance | host-catalogs | host sets | create', function (hooks) {
const name = 'azure host set';
await fillIn(commonSelectors.FIELD_NAME, name);
await fillIn(selectors.FIELD_PREFERRED_ENDPOINT, 'endpoint');
await click(selectors.FIELD_PREFERRED_ENDPOINT_ADD_BTN);
await fillIn(selectors.FIELD_AZURE_FILTER, 'filter');
await fillIn(selectors.FIELD_SYNC_INTERVAL, 10);
await click(commonSelectors.SAVE_BTN);
@@ -157,9 +154,7 @@ module('Acceptance | host-catalogs | host sets | create', function (hooks) {
const name = 'gcp host set';
await fillIn(commonSelectors.FIELD_NAME, name);
await fillIn(selectors.FIELD_PREFERRED_ENDPOINT, 'endpoint');
await click(selectors.FIELD_PREFERRED_ENDPOINT_ADD_BTN);
await fillIn(selectors.FIELD_FILTERS, 'filter_test');
await click(selectors.FIELD_FILTERS_ADD_BTN);
await fillIn(selectors.FIELD_SYNC_INTERVAL, 10);
await click(commonSelectors.SAVE_BTN);
@@ -3,17 +3,18 @@
* SPDX-License-Identifier: BUSL-1.1
*/
export const FIELD_PREFERRED_ENDPOINT = '[name=preferred_endpoints] input';
export const FIELD_PREFERRED_ENDPOINT =
'[name=preferred_endpoints] [data-test-value-input]';
export const FIELD_PREFERRED_ENDPOINT_ADD_BTN =
'[name=preferred_endpoints] button';
'[name=preferred_endpoints] [data-test-add-button]';
export const FIELD_PREFERRED_ENDPOINT_DELETE_BTN =
'[name=preferred_endpoints] [data-test-remove-button]';
export const FIELD_FILTERS = '[name=filters] input';
export const FIELD_FILTERS_ADD_BTN = '[name=filters] button';
'[name=preferred_endpoints] [data-test-delete-button]';
export const FIELD_FILTERS = '[name=filters] [data-test-value-input]';
export const FIELD_FILTERS_ADD_BTN = '[name=filters] [data-test-add-button]';
export const FIELD_SYNC_INTERVAL = '[name=sync_interval_seconds]';
export const FIELD_AZURE_FILTER = '[name=filter]';
export const FIELD_FILTERS_REMOVE_BTN =
'[name=filters] [data-test-remove-button]';
'[name=filters] [data-test-delete-button]';
export const MANAGE_DROPDOWN_HOST_CATALOG =
'[data-test-manage-host-catalogs-dropdown] button';
@@ -172,7 +172,6 @@ module('Acceptance | host-catalogs | host sets | update', function (hooks) {
);
await fillIn(selectors.FIELD_PREFERRED_ENDPOINT, mockEndpoint);
await click(selectors.FIELD_PREFERRED_ENDPOINT_ADD_BTN);
const filterList = await Promise.all(
findAll(selectors.FIELD_FILTERS_REMOVE_BTN),
@@ -184,7 +183,6 @@ module('Acceptance | host-catalogs | host sets | update', function (hooks) {
assert.strictEqual(findAll(selectors.FIELD_FILTERS_REMOVE_BTN).length, 0);
await fillIn(selectors.FIELD_FILTERS, mockFilter);
await click(selectors.FIELD_FILTERS_ADD_BTN);
await fillIn(selectors.FIELD_SYNC_INTERVAL, 10);
await click(commonSelectors.SAVE_BTN);
@@ -216,7 +214,6 @@ module('Acceptance | host-catalogs | host sets | update', function (hooks) {
);
await fillIn(selectors.FIELD_PREFERRED_ENDPOINT, mockEndpoint);
await click(selectors.FIELD_PREFERRED_ENDPOINT_ADD_BTN);
await fillIn(selectors.FIELD_AZURE_FILTER, 'filter');
await fillIn(selectors.FIELD_SYNC_INTERVAL, 10);
await click(commonSelectors.SAVE_BTN);
@@ -247,7 +244,6 @@ module('Acceptance | host-catalogs | host sets | update', function (hooks) {
);
await fillIn(selectors.FIELD_PREFERRED_ENDPOINT, 'sample endpoint');
await click(selectors.FIELD_PREFERRED_ENDPOINT_ADD_BTN);
// Remove all the filters
const filterList = await Promise.all(
@@ -260,7 +256,6 @@ module('Acceptance | host-catalogs | host sets | update', function (hooks) {
assert.strictEqual(findAll(selectors.FIELD_FILTERS_REMOVE_BTN).length, 0);
await fillIn(selectors.FIELD_FILTERS, 'sample filters');
await click(selectors.FIELD_FILTERS_ADD_BTN);
await fillIn(selectors.FIELD_SYNC_INTERVAL, 10);
await click(commonSelectors.SAVE_BTN);
@@ -18,9 +18,9 @@ module('Integration | Component | form/field/key-value', function (hooks) {
const getValueInputs = () => findAll('[data-test-value-input]');
const getDeleteButtons = () => findAll('[data-test-delete-button]');
test('it renders with default empty row', async function (assert) {
test('it renders with default and provided data', async function (assert) {
// Default empty row
this.set('data', [{ key: '', value: '' }]);
await render(hbs`
<Form::Field::KeyValue @data={{this.data}}>
<:row as |R|>
@@ -43,14 +43,12 @@ module('Integration | Component | form/field/key-value', function (hooks) {
assert.dom('[data-test-value-input]').exists();
assert.dom('[data-test-add-button]').exists();
assert.dom('[data-test-delete-button]').doesNotExist();
});
test('it renders with provided data', async function (assert) {
// Provided data
this.set('data', [
{ key: 'env', value: 'production' },
{ key: 'region', value: 'us-east-1' },
]);
await render(hbs`
<Form::Field::KeyValue
@data={{this.data}}
@@ -72,13 +70,10 @@ module('Integration | Component | form/field/key-value', function (hooks) {
</:footer>
</Form::Field::KeyValue>
`);
const keyInputs = getKeyInputs();
const valueInputs = getValueInputs();
assert.strictEqual(keyInputs.length, 2);
assert.strictEqual(valueInputs.length, 2);
assert.dom(keyInputs[0]).hasValue('env');
assert.dom(keyInputs[1]).hasValue('region');
assert.dom(valueInputs[0]).hasValue('production');
@@ -87,7 +82,7 @@ module('Integration | Component | form/field/key-value', function (hooks) {
assert.dom('.hds-form-helper-text ').hasText('Test helper text');
});
test('it adds new rows', async function (assert) {
test('it adds new rows using custom handlers', async function (assert) {
this.set('data', [{ key: '', value: '' }]);
this.set('onAdd', () => {
this.data.push({ key: '', value: '' });
@@ -107,7 +102,7 @@ module('Integration | Component | form/field/key-value', function (hooks) {
<F.TextInput data-test-value-input @value={{R.rowData.value}} {{on 'input' (fn R.updateRow 'value')}} />
</R.Field>
{{#if R.canDelete}}
<R.DeleteRowButton data-test-delete-button @onClick={{R.removeRow}} />
<R.DeleteRowButton data-test-delete-button @onClick={{R.removeRow}} />
{{/if}}
</:row>
<:footer as |F|>
@@ -117,89 +112,22 @@ module('Integration | Component | form/field/key-value', function (hooks) {
`);
assert.strictEqual(getKeyInputs().length, 1, 'Initially has one row');
await click('[data-test-add-button]');
assert.strictEqual(
getKeyInputs().length,
2,
'After clicking add, has two rows',
);
assert.strictEqual(getValueInputs().length, 2);
// Fill in the second row
await fillIn(getKeyInputs()[1], 'test-key');
await fillIn(getValueInputs()[1], 'test-value');
// Check that the data was updated
assert.strictEqual(this.data.length, 2);
assert.strictEqual(this.data[1].key, 'test-key');
assert.strictEqual(this.data[1].value, 'test-value');
});
test('it removes rows', async function (assert) {
this.set('data', [
{ key: 'key1', value: 'value1' },
{ key: 'key2', value: 'value2' },
]);
this.set('onRemove', (rowData) => {
// Remove the row from the data array
let newData = this.data.filter((item) => item !== rowData);
// Ensure at least one empty row remains
if (newData.length === 0) {
newData = [{ key: '', value: '' }];
}
this.set('data', newData);
});
await render(hbs`
<Form::Field::KeyValue @data={{this.data}} @onRemove={{this.onRemove}}>
<:row as |R|>
<R.Field as |F|>
<F.TextInput data-test-key-input @value={{R.rowData.key}} {{on 'input' (fn R.updateRow 'key')}} />
</R.Field>
<R.Field as |F|>
<F.TextInput data-test-value-input @value={{R.rowData.value}} {{on 'input' (fn R.updateRow 'value')}} />
</R.Field>
{{#if R.canDelete}}
<R.DeleteRowButton data-test-delete-button @onClick={{R.removeRow}} />
{{/if}}
</:row>
<:footer as |F|>
<F.AddRowButton />
</:footer>
</Form::Field::KeyValue>
`);
assert.strictEqual(getKeyInputs().length, 2, 'Initially has two rows');
// Remove first row
await click(getDeleteButtons()[0]);
assert.strictEqual(
getKeyInputs().length,
1,
'After removing first row, has one row',
);
assert.dom('[data-test-key-input]').hasValue('key2');
assert.dom('[data-test-value-input]').hasValue('value2');
// Remove second row - should ensure at least one empty row remains
await click(getDeleteButtons()[0]);
assert.strictEqual(
getKeyInputs().length,
1,
'After removing all rows, still has one empty row',
);
assert
.dom('[data-test-key-input]')
.hasValue('', 'The remaining row has empty key');
assert
.dom('[data-test-value-input]')
.hasValue('', 'The remaining row has empty value');
});
test('it handles different field types', async function (assert) {
this.set('data', [{ key: 'k', value: 'v', id: 1 }]);
@@ -282,6 +210,7 @@ module('Integration | Component | form/field/key-value', function (hooks) {
{ message: 'Value must be unique' },
]);
// With errors
await render(hbs`
<Form::Field::KeyValue @data={{this.data}} @errors={{this.errors}}>
<:row as |R|>
@@ -300,18 +229,15 @@ module('Integration | Component | form/field/key-value', function (hooks) {
</:footer>
</Form::Field::KeyValue>
`);
assert.dom('[data-test-error-message]').exists().hasText('Key is required');
assert
.dom('[data-test-error-message]:nth-child(2)')
.hasText('Value must be unique');
});
test('it does not show error messages when no errors are provided', async function (assert) {
this.set('data', [{ key: '', value: '' }]);
// Without errors
this.set('errors', undefined);
await render(hbs`
<Form::Field::KeyValue @data={{this.data}}>
<Form::Field::KeyValue @data={{this.data}} @errors={{this.errors}}>
<:row as |R|>
<R.Field as |F|>
<F.TextInput data-test-key-input @value={{R.rowData.key}} />
@@ -328,11 +254,10 @@ module('Integration | Component | form/field/key-value', function (hooks) {
</:footer>
</Form::Field::KeyValue>
`);
assert.dom('[data-test-error-message]').doesNotExist();
});
test('it updates both key and value fields', async function (assert) {
test('it updates both key and value fields using custom handlers', async function (assert) {
this.set('data', [{ key: '', value: '' }]);
this.set('onUpdate', (rowData, property, { target: { value } }) => {
rowData[property] = value;
@@ -393,4 +318,174 @@ module('Integration | Component | form/field/key-value', function (hooks) {
assert.strictEqual(getDeleteButtons().length, 3);
});
test('custom handlers take precedence over default handlers', async function (assert) {
const model = {
tags: [{ key: '', value: '' }],
};
this.set('model', model);
this.set('onAdd', () => {
model.tags.push({ key: 'custom', value: 'custom' });
this.set('model', { ...model, tags: [...model.tags] });
});
this.set('onRemove', (rowData) => {
model.tags = model.tags.filter((item) => item !== rowData);
this.set('model', { ...model, tags: [...model.tags] });
});
await render(hbs`
<Form::Field::KeyValue
@model={{this.model}}
@name="tags"
@properties={{array 'key' 'value'}}
@data={{this.model.tags}}
@onAdd={{this.onAdd}}
@onRemove={{this.onRemove}}
@onUpdate={{this.onUpdate}}>
<:row as |R|>
<R.Field as |F|>
<F.TextInput data-test-key-input @value={{R.rowData.key}} {{on 'input' (fn R.updateRow 'key')}} />
</R.Field>
<R.Field as |F|>
<F.TextInput data-test-value-input @value={{R.rowData.value}} {{on 'input' (fn R.updateRow 'value')}} />
</R.Field>
{{#if R.canDelete}}
<R.DeleteRowButton data-test-delete-button @onClick={{R.removeRow}} />
{{/if}}
</:row>
</Form::Field::KeyValue>
`);
// Test custom add handler takes precedence
await click('[data-test-add-button]');
assert.strictEqual(model.tags.length, 2);
assert.strictEqual(model.tags[1].key, 'custom');
assert.strictEqual(model.tags[1].value, 'custom');
// Test custom update handler takes precedence
await fillIn(getKeyInputs()[0], 'test');
// Test custom remove handler takes precedence
await click(getDeleteButtons()[0]);
assert.strictEqual(model.tags.length, 1);
assert.strictEqual(model.tags[0].key, 'custom');
});
test('it adds row using default handler (model/name/properties)', async function (assert) {
const model = { tags: [{ key: 'a', value: 'b' }] };
this.set('model', model);
this.set('properties', ['key', 'value']);
await render(hbs`
<Form::Field::KeyValue
@model={{this.model}}
@name="tags"
@properties={{this.properties}}
@data={{this.model.tags}}>
<:row as |R|>
<R.Field as |F|>
<F.TextInput data-test-key-input @value={{R.rowData.key}} />
</R.Field>
<R.Field as |F|>
<F.TextInput data-test-value-input @value={{R.rowData.value}} />
</R.Field>
{{#if R.canDelete}}
<R.DeleteRowButton data-test-delete-button @onClick={{R.removeRow}} />
{{/if}}
</:row>
<:footer as |F|>
<F.AddRowButton />
</:footer>
</Form::Field::KeyValue>
`);
assert.strictEqual(model.tags.length, 1);
await click('[data-test-add-button]');
assert.strictEqual(model.tags.length, 2);
assert.deepEqual(model.tags[1], { key: '', value: '' });
});
test('it removes row using default handler (model/name)', async function (assert) {
const model = {
tags: [
{ key: 'a', value: 'b' },
{ key: 'c', value: 'd' },
],
};
this.set('model', model);
this.set('properties', ['key', 'value']);
await render(hbs`
<Form::Field::KeyValue
@model={{this.model}}
@name="tags"
@properties={{this.properties}}
@data={{this.model.tags}}>
<:row as |R|>
<R.Field as |F|>
<F.TextInput data-test-key-input @value={{R.rowData.key}} />
</R.Field>
<R.Field as |F|>
<F.TextInput data-test-value-input @value={{R.rowData.value}} />
</R.Field>
{{#if R.canDelete}}
<R.DeleteRowButton data-test-delete-button @onClick={{R.removeRow}} />
{{/if}}
</:row>
<:footer as |F|>
<F.AddRowButton />
</:footer>
</Form::Field::KeyValue>
`);
assert.strictEqual(model.tags.length, 2);
await click(getDeleteButtons()[0]);
assert.strictEqual(model.tags.length, 1);
assert.deepEqual(model.tags[0], { key: 'c', value: 'd' });
});
test('it updates row using default handler (model/name)', async function (assert) {
const model = { tags: [{ key: '', value: '' }] };
this.set('model', model);
this.set('properties', ['key', 'value']);
await render(hbs`
<Form::Field::KeyValue
@model={{this.model}}
@name="tags"
@properties={{this.properties}}
@data={{this.model.tags}}>
<:row as |R|>
<R.Field as |F|>
<F.TextInput data-test-key-input @value={{R.rowData.key}} {{on 'input' (fn R.updateRow 'key')}} />
</R.Field>
<R.Field as |F|>
<F.TextInput data-test-value-input @value={{R.rowData.value}} {{on 'input' (fn R.updateRow 'value')}} />
</R.Field>
{{#if R.canDelete}}
<R.DeleteRowButton data-test-delete-button @onClick={{R.removeRow}} />
{{/if}}
</:row>
<:footer as |F|>
<F.AddRowButton />
</:footer>
</Form::Field::KeyValue>
`);
await fillIn('[data-test-key-input]', 'updated-key');
await fillIn('[data-test-value-input]', 'updated-value');
assert.strictEqual(model.tags[0].key, 'updated-key');
assert.strictEqual(model.tags[0].value, 'updated-value');
});
});