Skip to content

Commit bb0a296

Browse files
authored
fix(preprint-details): updated logic for truncated abstract (#933)
- Ticket: [ENG-10735] - Feature flag: n/a ## Summary of Changes 1. Added logic to expand text. 2. Added expanded text for abstract in preprints.
1 parent f806275 commit bb0a296

7 files changed

Lines changed: 91 additions & 47 deletions

File tree

src/app/features/preprints/components/preprint-details/general-information/general-information.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ <h3>{{ 'preprints.preprintStepper.review.sections.metadata.authors' | translate
1919

2020
<section class="flex flex-column gap-2">
2121
<h3>{{ 'preprints.preprintStepper.common.labels.abstract' | translate }}</h3>
22-
<osf-truncated-text [maxVisibleLines]="2" [text]="preprintValue.description" />
22+
<osf-truncated-text [expanded]="!isBrowser" [maxVisibleLines]="2" [text]="preprintValue.description" />
2323
</section>
2424

2525
@if (affiliatedInstitutions().length) {

src/app/features/preprints/components/preprint-details/general-information/general-information.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ import { PreprintDoiSectionComponent } from '../preprint-doi-section/preprint-do
5757
export class GeneralInformationComponent implements OnDestroy {
5858
private readonly environment = inject(ENVIRONMENT);
5959
private readonly platformId = inject(PLATFORM_ID);
60-
private readonly isBrowser = isPlatformBrowser(this.platformId);
60+
readonly isBrowser = isPlatformBrowser(this.platformId);
6161

6262
readonly preprintProvider = input.required<PreprintProviderDetails | undefined>();
6363
readonly preprintVersionSelected = output<string>();

src/app/features/preprints/components/preprint-details/preprint-tombstone/preprint-tombstone.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ <h3>{{ 'preprints.preprintStepper.review.sections.metadata.authors' | translate
2525

2626
<section class="flex flex-column gap-2">
2727
<h3>{{ 'preprints.preprintStepper.common.labels.abstract' | translate }}</h3>
28-
<osf-truncated-text [maxVisibleLines]="2" [text]="preprintValue.description" />
28+
<osf-truncated-text [expanded]="!isBrowser" [maxVisibleLines]="2" [text]="preprintValue.description" />
2929
</section>
3030

3131
<osf-preprint-doi-section

src/app/features/preprints/components/preprint-details/preprint-tombstone/preprint-tombstone.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ import { PreprintDoiSectionComponent } from '../preprint-doi-section/preprint-do
5656
})
5757
export class PreprintTombstoneComponent implements OnDestroy {
5858
private readonly router = inject(Router);
59-
private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
59+
readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
6060

6161
readonly preprintProvider = input.required<PreprintProviderDetails | undefined>();
6262
readonly preprintVersionSelected = output<string>();
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
<div class="flex flex-column gap-3">
2-
<div #textContent class="text-content" [class.expanded]="isTextExpanded()" [style.--line-clamp]="maxVisibleLines()">
2+
<div #textContent class="text-content" [class.expanded]="isExpanded()" [style.--line-clamp]="maxVisibleLines()">
33
@if (hasOwnContent()) {
44
<ng-content></ng-content>
55
} @else {
66
<div [innerHTML]="text()"></div>
77
}
88
</div>
99

10-
@if (hasOverflowingText()) {
10+
@if (hasOverflowingText() && !expanded()) {
1111
<p-button class="link-btn-no-padding" link [label]="buttonLabel() | translate" (onClick)="handleButtonClick()" />
1212
}
1313
</div>
Lines changed: 79 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,121 @@
1+
import { MockProvider } from 'ng-mocks';
2+
13
import { ComponentFixture, TestBed } from '@angular/core/testing';
4+
import { Router } from '@angular/router';
25

36
import { TruncatedTextComponent } from './truncated-text.component';
47

5-
import { TranslateServiceMock } from '@testing/mocks/translate.service.mock';
8+
import { provideOSFCore } from '@testing/osf.testing.provider';
9+
import { RouterMockBuilder, RouterMockType } from '@testing/providers/router-provider.mock';
610

711
describe('TruncatedTextComponent', () => {
812
let component: TruncatedTextComponent;
913
let fixture: ComponentFixture<TruncatedTextComponent>;
14+
let mockRouter: RouterMockType;
15+
16+
beforeEach(() => {
17+
mockRouter = RouterMockBuilder.create().build();
1018

11-
beforeEach(async () => {
12-
await TestBed.configureTestingModule({
19+
TestBed.configureTestingModule({
1320
imports: [TruncatedTextComponent],
14-
providers: [TranslateServiceMock],
15-
}).compileComponents();
21+
providers: [provideOSFCore(), MockProvider(Router, mockRouter)],
22+
});
1623

1724
fixture = TestBed.createComponent(TruncatedTextComponent);
1825
component = fixture.componentInstance;
1926
});
2027

2128
it('should create', () => {
29+
fixture.detectChanges();
2230
expect(component).toBeTruthy();
2331
});
2432

25-
it('should set text input correctly', () => {
26-
fixture.componentRef.setInput('text', 'Test text content');
27-
expect(component.text()).toBe('Test text content');
28-
});
29-
30-
it('should set maxVisibleLines input correctly', () => {
33+
it('should reflect text, maxVisibleLines, and expanded inputs', () => {
34+
fixture.componentRef.setInput('text', 'hello');
3135
fixture.componentRef.setInput('maxVisibleLines', 5);
36+
fixture.componentRef.setInput('expanded', true);
37+
38+
expect(component.text()).toBe('hello');
3239
expect(component.maxVisibleLines()).toBe(5);
40+
expect(component.expanded()).toBe(true);
3341
});
3442

35-
it('should call checkTextOverflow in ngAfterViewInit', () => {
36-
const checkTextOverflowSpy = jest.spyOn(component as any, 'checkTextOverflow');
43+
it('should derive isExpanded from expanded input or user expansion', () => {
44+
expect(component.isExpanded()).toBe(false);
3745

38-
component.ngAfterViewInit();
46+
fixture.componentRef.setInput('expanded', true);
47+
expect(component.isExpanded()).toBe(true);
3948

40-
expect(checkTextOverflowSpy).toHaveBeenCalledTimes(1);
49+
fixture.componentRef.setInput('expanded', false);
50+
component.isTextExpanded.set(true);
51+
expect(component.isExpanded()).toBe(true);
4152
});
4253

43-
it('should handle empty text input', () => {
44-
fixture.componentRef.setInput('text', '');
45-
expect(component.text()).toBe('');
54+
it('should compute buttonLabel for read-more vs hide when not navigating', () => {
55+
fixture.componentRef.setInput('navigateOnReadMore', false);
56+
fixture.componentRef.setInput('readMoreLabel', 'read');
57+
fixture.componentRef.setInput('hideLabel', 'hide');
58+
59+
expect(component.buttonLabel()).toBe('read');
60+
61+
component.isTextExpanded.set(true);
62+
expect(component.buttonLabel()).toBe('hide');
4663
});
4764

48-
it('should handle different maxVisibleLines values', () => {
49-
const values = [1, 2, 3, 5, 10, 100];
65+
it('should use readMoreLabel for buttonLabel when navigateOnReadMore is true', () => {
66+
fixture.componentRef.setInput('navigateOnReadMore', true);
67+
fixture.componentRef.setInput('readMoreLabel', 'go');
5068

51-
values.forEach((value) => {
52-
fixture.componentRef.setInput('maxVisibleLines', value);
53-
expect(component.maxVisibleLines()).toBe(value);
54-
});
69+
expect(component.buttonLabel()).toBe('go');
70+
71+
component.isTextExpanded.set(true);
72+
expect(component.buttonLabel()).toBe('go');
5573
});
5674

57-
it('should handle negative maxVisibleLines', () => {
58-
fixture.componentRef.setInput('maxVisibleLines', -1);
59-
expect(component.maxVisibleLines()).toBe(-1);
75+
it('should call checkTextOverflow from ngAfterViewInit', () => {
76+
const checkTextOverflowSpy = jest.spyOn(component, 'checkTextOverflow');
77+
78+
component.ngAfterViewInit();
79+
80+
expect(checkTextOverflowSpy).toHaveBeenCalledTimes(1);
81+
});
82+
83+
it('should reset user expansion when text changes to a new non-empty value', () => {
84+
jest.useFakeTimers();
85+
try {
86+
fixture.componentRef.setInput('text', 'first');
87+
fixture.detectChanges();
88+
jest.advanceTimersByTime(0);
89+
90+
component.isTextExpanded.set(true);
91+
fixture.componentRef.setInput('text', 'second');
92+
fixture.detectChanges();
93+
jest.advanceTimersByTime(0);
94+
95+
expect(component.isTextExpanded()).toBe(false);
96+
} finally {
97+
jest.useRealTimers();
98+
}
6099
});
61100

62-
it('should properly update isTextExpanded signal', () => {
63-
expect(component['isTextExpanded']()).toBe(false);
101+
it('should toggle isTextExpanded on handleButtonClick when not navigateOnReadMore', () => {
102+
fixture.componentRef.setInput('navigateOnReadMore', false);
103+
104+
expect(component.isTextExpanded()).toBe(false);
64105

65-
component['isTextExpanded'].set(true);
66-
expect(component['isTextExpanded']()).toBe(true);
106+
component.handleButtonClick();
107+
expect(component.isTextExpanded()).toBe(true);
67108

68-
component['isTextExpanded'].set(false);
69-
expect(component['isTextExpanded']()).toBe(false);
109+
component.handleButtonClick();
110+
expect(component.isTextExpanded()).toBe(false);
70111
});
71112

72-
it('should properly update hasOverflowingText signal', () => {
73-
expect(component['hasOverflowingText']()).toBe(false);
113+
it('should navigate when navigateOnReadMore is true', () => {
114+
fixture.componentRef.setInput('navigateOnReadMore', true);
115+
fixture.componentRef.setInput('link', ['/preprints', '123']);
74116

75-
component['hasOverflowingText'].set(true);
76-
expect(component['hasOverflowingText']()).toBe(true);
117+
component.handleButtonClick();
77118

78-
component['hasOverflowingText'].set(false);
79-
expect(component['hasOverflowingText']()).toBe(false);
119+
expect(mockRouter.navigate).toHaveBeenCalledWith(['/preprints', '123']);
80120
});
81121
});

src/app/shared/components/truncated-text/truncated-text.component.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { timer } from 'rxjs';
77
import {
88
AfterViewInit,
99
Component,
10+
computed,
1011
DestroyRef,
1112
effect,
1213
ElementRef,
@@ -26,6 +27,7 @@ import { Router } from '@angular/router';
2627
})
2728
export class TruncatedTextComponent implements AfterViewInit {
2829
readonly text = input('');
30+
readonly expanded = input(false);
2931
readonly maxVisibleLines = input(3);
3032
readonly navigateOnReadMore = input(false);
3133
readonly link = input<string[]>([]);
@@ -40,13 +42,15 @@ export class TruncatedTextComponent implements AfterViewInit {
4042
isTextExpanded = signal(false);
4143
hasOverflowingText = signal(false);
4244

43-
buttonLabel = () => {
45+
isExpanded = computed(() => this.expanded() || this.isTextExpanded());
46+
47+
buttonLabel = computed(() => {
4448
if (this.navigateOnReadMore()) {
4549
return this.readMoreLabel();
4650
}
4751

4852
return this.isTextExpanded() ? this.hideLabel() : this.readMoreLabel();
49-
};
53+
});
5054

5155
constructor() {
5256
effect(() => {

0 commit comments

Comments
 (0)