Skip to content
Merged
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
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,23 @@ so it stays clear which part of the repository actually moved.
family-wide: `true` silences every bundle loaded through this service, in every
`@dignite/ng.flex-fields*` package. (#232)

### Fixed

#### flex-fields

- **`DateTimeViewComponent`, the read-only view for `DateTime` fields, ignored the field's
`DateTime.InputMode` configuration and always rendered `value | shortDateTime`.** A field configured
for `InputMode = Date` or `InputMode = Month` therefore showed a spurious time part in every
read-only context — a bare field, or a `Table` column rendered through `ff-table-view`, which
dispatches to this same component via `ff-flex-field-view`. The edit-mode counterpart,
`DateTimeControlComponent`, already read `configuration['DateTime.InputMode']`, looked up the
matching Angular `DatePipe` format string in `DATE_INPUT_MODE_FORMATS`, and formatted with it — the
view component never did the same lookup despite receiving the same `field.configuration` on its
`fields` input. `DateTimeViewComponent` now performs that lookup itself, injecting `DatePipe` the
same way, and falls back to the `shortDateTime` pipe only when no `fields` input is bound or the
configured mode isn't in the table, so existing usages that never pass `field.configuration` keep
rendering exactly as before.

## [10.0.0-rc.16] - 2026-09-05

### Fixed
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
@if (showInList) {
{{ value | shortDateTime }}
{{ formattedValue ?? (value | shortDateTime) }}
} @else {
<div class="mb-3">
<label class="form-label" *ngIf="fields?.field?.displayName">
{{ fields.field.displayName }}
</label>
<div>{{ value | shortDateTime }}</div>
<div>{{ formattedValue ?? (value | shortDateTime) }}</div>
</div>
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,36 @@
import { TestBed } from '@angular/core/testing';
import { ConfigStateService } from '@abp/ng.core';
import { CoreTestingModule } from '@abp/ng.core/testing';
import { FlexFieldValue } from '../../models';
import { DateTimeViewComponent } from './date-time-view.component';
import { DateTimeInputMode } from './date-time-input-mode';

function fieldValue(overrides: Partial<FlexFieldValue> = {}): FlexFieldValue {
return {
field: {
id: '1',
name: 'publishedAt',
displayName: 'Published',
fieldTypeName: 'DateTime',
configuration: {},
},
required: false,
searchable: false,
...overrides,
};
}

function withMode(mode: DateTimeInputMode): FlexFieldValue {
return fieldValue({
field: {
id: '1',
name: 'publishedAt',
displayName: 'Published',
fieldTypeName: 'DateTime',
configuration: { 'DateTime.InputMode': mode },
},
});
}

describe('DateTimeViewComponent', () => {
beforeEach(() => {
Expand All @@ -20,22 +49,57 @@ describe('DateTimeViewComponent', () => {
} as unknown as Parameters<ConfigStateService['setState']>[0]);
});

function render(value: unknown, showInList = false) {
function render(value: unknown, options: { showInList?: boolean; fields?: FlexFieldValue } = {}) {
const fixture = TestBed.createComponent(DateTimeViewComponent);
fixture.componentRef.setInput('value', value);
fixture.componentRef.setInput('showInList', showInList);
fixture.componentRef.setInput('showInList', options.showInList ?? false);
if (options.fields) {
fixture.componentRef.setInput('fields', options.fields);
}
fixture.detectChanges();
return fixture;
}

// Local-time construction, not an ISO `Z` string: DatePipe formats in local time, so building the
// expectation from the same local wall-clock value keeps the test independent of the machine's TZ.
const localDateTime = new Date(2026, 7, 17, 10, 30, 0);

it('renders nothing for an unset value instead of throwing', () => {
expect(() => render('')).not.toThrow();
});

it('renders a formatted value both in and out of list mode', () => {
const localDateTime = new Date(2026, 7, 17, 10, 30, 0);
it('falls back to the short date-time format when no configuration is available', () => {
expect(render(localDateTime, { showInList: true }).nativeElement.textContent).toContain('2026');
expect(render(localDateTime, { showInList: false }).nativeElement.textContent).toContain('2026');
});

it('formats the value to the Date input mode, without a time part', () => {
const text = render(localDateTime, { fields: withMode(DateTimeInputMode.Date) }).nativeElement.textContent;

expect(text).toContain('2026-08-17');
expect(text).not.toContain('10:30');
});

it('formats the value to the DateTime input mode', () => {
const text = render(localDateTime, { fields: withMode(DateTimeInputMode.DateTime) }).nativeElement.textContent;

expect(text).toContain('2026-08-17 10:30:00');
});

it('formats the value to the Month input mode, without a day part', () => {
const text = render(localDateTime, { fields: withMode(DateTimeInputMode.Month) }).nativeElement.textContent;

expect(text).toContain('2026-08');
expect(text).not.toContain('2026-08-17');
});

it('applies the configured input mode in list mode too, e.g. a Table-nested column', () => {
const text = render(localDateTime, {
showInList: true,
fields: withMode(DateTimeInputMode.Date),
}).nativeElement.textContent;

expect(render(localDateTime, true).nativeElement.textContent).toContain('2026');
expect(render(localDateTime, false).nativeElement.textContent).toContain('2026');
expect(text).toContain('2026-08-17');
expect(text).not.toContain('10:30');
});
});
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
import { Component, Input } from '@angular/core';
import { Component, inject, Input } from '@angular/core';
import { CoreModule } from '@abp/ng.core';
import { DatePipe } from '@angular/common';
import { FlexFieldValue } from '../../models';
import { DATE_INPUT_MODE_FORMATS } from './date-time-configuration';
import { DateTimeInputMode } from './date-time-input-mode';

/** Displays the value of a `DateTime` field read-only, in the user's short date-time format. */
/**
* Displays the value of a `DateTime` field read-only, formatted per its `DateTime.InputMode`
* configuration (mirroring `date-time-control.component.ts`'s edit-mode formatting). Falls back to
* the user's short date-time format when no configuration is available to read a mode from.
*/
@Component({
selector: 'ff-date-time-view',
templateUrl: './date-time-view.component.html',
imports: [CoreModule],
providers: [DatePipe],
})
export class DateTimeViewComponent {
private readonly datePipe = inject(DatePipe);

/** Renders bare, without the label wrapper, for use inside a table cell. */
@Input() showInList = false;

Expand All @@ -18,4 +28,11 @@ export class DateTimeViewComponent {
@Input() type?: string;

@Input() value: unknown = '';

/** `undefined` when `fields` carries no known `InputMode`, so the template falls back to `shortDateTime`. */
get formattedValue(): string | null | undefined {
const mode = this.fields?.field.configuration['DateTime.InputMode'] as DateTimeInputMode;
const format = DATE_INPUT_MODE_FORMATS[mode]?.format;
return format ? this.datePipe.transform(this.value as string, format) : undefined;
}
}