Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions packages/base/contains-many-component.gts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,11 @@ class ContainsManyEditor extends GlimmerComponent<ContainsManyEditorSignature> {

<template>
<PermissionsConsumer as |permissions|>
<div class='contains-many-editor' data-test-contains-many={{@field.name}}>
<div
class='contains-many-editor'
data-card-field={{@field.name}}
data-test-contains-many={{@field.name}}
>
{{#if this.decoratedChildren.length}}
<ul
{{sortableGroup
Expand Down Expand Up @@ -144,7 +148,12 @@ class ContainsManyEditor extends GlimmerComponent<ContainsManyEditorSignature> {
{{on 'click' this.add}}
data-test-add-new
>
<IconPlus class='icon' width='12px' height='12px' role='presentation' />
<IconPlus
class='icon'
width='12px'
height='12px'
role='presentation'
/>
Add
{{getPlural @field.card.displayName}}
</Button>
Expand Down Expand Up @@ -362,6 +371,7 @@ export function getContainsManyComponent({
class='plural-field containsMany-field
{{effectiveFormat}}-format
{{unless arrayField.children.length "empty"}}'
data-card-field={{field.name}}
data-test-plural-view={{field.fieldType}}
data-test-plural-view-field={{field.name}}
data-test-plural-view-format={{effectiveFormat}}
Expand Down
15 changes: 15 additions & 0 deletions packages/base/field-component.gts
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,19 @@ export function getBoxComponent(
}}
{{#if (isCard model.value)}}
{{#let model.value as |card|}}
{{! A rendered field boundary carries
data-card-field=<fieldName> so selector-based
screenshot capture and region discovery can address
fields in templates that never opted in. That is: this
card-as-field container and the compound-field wrapper
below, plus the plural wrappers in
contains-many-component and links-to-many-component —
both their view (`plural-field`) and edit
(`*-editor`) forms. Stamped unconditionally; inert for
CSS. Omitted where no boundary element is rendered — a
card at the root (no field context), and a primitive
leaf field, which renders bare with no wrapper to
carry it. }}
<DefaultFormatsProvider
@value={{defaultFieldFormats effectiveFormats.cardDef}}
>
Expand All @@ -380,6 +393,7 @@ export function getBoxComponent(
}}
data-boxel-card-id={{card.id}}
data-boxel-card-format={{effectiveFormats.cardDef}}
data-card-field={{field.name}}
data-test-card={{card.id}}
data-test-card-format={{effectiveFormats.cardDef}}
data-test-field-component-card
Expand Down Expand Up @@ -420,6 +434,7 @@ export function getBoxComponent(
<div
class='compound-field
{{effectiveFormats.fieldDef}}-format'
data-card-field={{field.name}}
data-test-compound-field-format={{effectiveFormats.fieldDef}}
data-test-compound-field-component
...attributes
Expand Down
14 changes: 12 additions & 2 deletions packages/base/links-to-many-component.gts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,11 @@ class LinksToManyEditor extends GlimmerComponent<Signature> {
@consume(RealmURLContextName) declare realmURL: URL | undefined;

<template>
<div class='links-to-many-editor' data-test-links-to-many={{@field.name}}>
<div
class='links-to-many-editor'
data-card-field={{@field.name}}
data-test-links-to-many={{@field.name}}
>
{{#if (eq @childFormat 'atom')}}
<LinksToManyCompactEditor
@model={{@model}}
Expand Down Expand Up @@ -337,7 +341,12 @@ class LinksToManyStandardEditor extends GlimmerComponent<LinksToManyStandardEdit
{{on 'click' @add}}
data-test-add-new={{@field.name}}
>
<IconPlus class='icon' width='12px' height='12px' role='presentation' />
<IconPlus
class='icon'
width='12px'
height='12px'
role='presentation'
/>
Add
{{getPlural @field.card.displayName}}
</Button>
Expand Down Expand Up @@ -703,6 +712,7 @@ export function getLinksToManyComponent({
{{effectiveFormat}}-effectiveFormat
{{unless arrayField.children.length "empty"}}
display-container-{{displayContainer}}'
data-card-field={{field.name}}
data-test-plural-view-field={{field.name}}
data-test-plural-view={{field.fieldType}}
data-test-plural-view-format={{effectiveFormat}}
Expand Down
100 changes: 100 additions & 0 deletions packages/host/tests/integration/components/card-basics-test.gts
Original file line number Diff line number Diff line change
Expand Up @@ -982,6 +982,106 @@ module('Integration | card-basics', function (hooks) {
assert.dom('[data-test="number"]').containsText('10');
});

// The capture/discovery contract: a rendered field boundary — a card
// rendered as a field, a compound field's wrapper, and the plural wrappers
// in both their view and edit forms — carries data-card-field=<fieldName>,
// so selector-based screenshot capture and region discovery can address
// fields in templates that never opted in. The card root itself carries no
// field context, so it must not carry the attribute.
test('rendered field boundaries carry data-card-field', async function (assert) {
class Guest extends FieldDef {
@field name = contains(StringField);
static embedded = class Embedded extends Component<typeof this> {
<template>
<span><@fields.name /></span>
</template>
};
}

class Pet extends CardDef {
@field name = contains(StringField);
static embedded = class Embedded extends Component<typeof this> {
<template>
<span><@fields.name /></span>
</template>
};
}

class Person extends CardDef {
@field guest = contains(Guest);
@field nicknames = containsMany(StringField);
@field pet = linksTo(Pet);
@field pets = linksToMany(Pet);
static isolated = class Isolated extends Component<typeof this> {
<template>
<@fields.guest />
<@fields.nicknames />
<@fields.pet />
<@fields.pets />
</template>
};
}

loader.shimModule(`${testRealmURL}test-cards`, { Person, Pet });

let mango = new Pet({ name: 'Mango' });
let vanGogh = new Pet({ name: 'Van Gogh' });
let person = new Person({
guest: new Guest({ name: 'Hassan' }),
nicknames: ['Art', 'Arty'],
pet: mango,
pets: [mango, vanGogh],
});
await saveCard(mango, `${testRealmURL}Pet/mango`, loader);
await saveCard(vanGogh, `${testRealmURL}Pet/van-gogh`, loader);
await saveCard(person, `${testRealmURL}Person/arthur`, loader);

await renderCard(loader, person, 'isolated');

assert
.dom('[data-test-compound-field-component][data-card-field="guest"]')
.exists('a compound field wrapper carries its field name');
assert
.dom(
'[data-test-plural-view-field="nicknames"][data-card-field="nicknames"]',
)
.exists('a containsMany plural wrapper carries its field name');
assert
.dom(
`[data-card-field="pet"][data-test-card="${testRealmURL}Pet/mango"]`,
)
.exists('a linksTo card boundary carries its field name');
assert
.dom('[data-test-plural-view-field="pets"][data-card-field="pets"]')
.exists('a linksToMany plural wrapper carries its field name');
assert
.dom(
'[data-card-field="pets"] [data-card-field="pets"][data-test-field-component-card]',
)
.exists(
{ count: 2 },
'each linksToMany item boundary carries the plural field name',
);
assert
.dom(`[data-test-card="${testRealmURL}Person/arthur"]`)
.doesNotHaveAttribute(
'data-card-field',
'the card root has no field context, so no data-card-field',
);

// Edit format renders plural fields through their own editor wrappers,
// distinct elements from the view-format plural wrappers above. A
// containsMany of primitives has no per-item boundary, so the editor
// wrapper is the only element that can name the field for discovery.
await renderCard(loader, person, 'edit');
assert
.dom('.contains-many-editor[data-card-field="nicknames"]')
.exists('a containsMany editor wrapper carries its field name');
assert
.dom('.links-to-many-editor[data-card-field="pets"]')
.exists('a linksToMany editor wrapper carries its field name');
});

test('render a field in atom format', async function (assert) {
class EmphasizedString extends FieldDef {
static [primitive]: string;
Expand Down
Loading