• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

atinc / ngx-tethys / 68ef226c-f83e-44c1-b8ed-e420a83c5d84

28 May 2025 10:31AM UTC coverage: 10.352% (-80.0%) from 90.316%
68ef226c-f83e-44c1-b8ed-e420a83c5d84

Pull #3460

circleci

pubuzhixing8
chore: xxx
Pull Request #3460: refactor(icon): migrate signal input #TINFR-1476

132 of 6823 branches covered (1.93%)

Branch coverage included in aggregate %.

10 of 14 new or added lines in 1 file covered. (71.43%)

11648 existing lines in 344 files now uncovered.

2078 of 14525 relevant lines covered (14.31%)

6.69 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

9.09
/src/avatar/avatar.component.ts
1
import {
2
    ChangeDetectionStrategy,
3
    Component,
4
    ElementRef,
5
    Signal,
6
    WritableSignal,
7
    computed,
8
    effect,
9
    inject,
10
    input,
11
    model,
1✔
12
    output,
1✔
13
    signal
1✔
14
} from '@angular/core';
15
import { SafeHtml } from '@angular/platform-browser';
16
import { isString, coerceBooleanProperty } from 'ngx-tethys/util';
17
import { useHostRenderer } from '@tethys/cdk/dom';
18
import { ThyAvatarService } from './avatar.service';
19
import { AvatarShortNamePipe, AvatarBgColorPipe, AvatarSrcPipe } from './avatar.pipe';
20
import { ThyIcon } from 'ngx-tethys/icon';
21
import { NgClass, NgStyle } from '@angular/common';
22

23
const sizeArray = [16, 22, 24, 28, 32, 36, 44, 48, 68, 110, 160];
24

25
export const DEFAULT_SIZE = 36;
1✔
26

UNCOV
27
export const thyAvatarSizeMap = {
×
UNCOV
28
    xxs: 22,
×
UNCOV
29
    xs: 24,
×
UNCOV
30
    sm: 32,
×
UNCOV
31
    md: 36,
×
UNCOV
32
    lg: 48
×
UNCOV
33
};
×
UNCOV
34

×
UNCOV
35
/** https://html.spec.whatwg.org/multipage/embedded-content.html#attr-img-loading */
×
UNCOV
36
export type ThyAvatarLoading = 'eager' | 'lazy';
×
37

UNCOV
38
/** https://wicg.github.io/priority-hints/#idl-index */
×
UNCOV
39
export type ThyAvatarFetchPriority = 'high' | 'low' | 'auto';
×
40

UNCOV
41
/**
×
42
 * 头像组件
UNCOV
43
 * @name thy-avatar
×
UNCOV
44
 * @order 10
×
UNCOV
45
 */
×
UNCOV
46
@Component({
×
47
    selector: 'thy-avatar',
UNCOV
48
    templateUrl: './avatar.component.html',
×
UNCOV
49
    host: {
×
UNCOV
50
        class: 'thy-avatar'
×
UNCOV
51
    },
×
52
    changeDetection: ChangeDetectionStrategy.OnPush,
UNCOV
53
    imports: [NgClass, NgStyle, ThyIcon, AvatarShortNamePipe, AvatarBgColorPipe, AvatarSrcPipe]
×
54
})
UNCOV
55
export class ThyAvatar {
×
UNCOV
56
    private thyAvatarService = inject(ThyAvatarService);
×
UNCOV
57
    elementRef = inject(ElementRef);
×
UNCOV
58

×
UNCOV
59
    /**
×
60
     * * 已废弃,请使用 thyRemove
61
     * @deprecated
UNCOV
62
     */
×
UNCOV
63
    readonly thyOnRemove = output<Event>();
×
64

65
    /**
UNCOV
66
     *  移除按钮的事件,当 thyRemovable 为 true 时起作用
×
UNCOV
67
     */
×
UNCOV
68
    readonly thyRemove = output<Event>();
×
UNCOV
69

×
UNCOV
70
    /**
×
UNCOV
71
     *  头像 img 加载 error 时触发
×
UNCOV
72
     */
×
UNCOV
73
    readonly thyError = output<Event>();
×
UNCOV
74

×
UNCOV
75
    /**
×
UNCOV
76
     * 是否展示人员名称
×
77
     */
78
    readonly thyShowName = input(false, { transform: coerceBooleanProperty });
79

UNCOV
80
    /**
×
UNCOV
81
     * 头像路径地址, 默认为全路径,如果不是全路径,可以通过自定义服务 ThyAvatarService,重写 srcTransform 方法实现转换
×
UNCOV
82
     */
×
UNCOV
83
    readonly thySrc = input<string>();
×
UNCOV
84

×
UNCOV
85
    readonly src = computed(() => {
×
UNCOV
86
        if (this.isAvatarImgError()) {
×
87
            return null;
88
        }
UNCOV
89
        if (this.thySrc() && this.thyAvatarService.ignoreAvatarSrcPaths.indexOf(this.thySrc()) < 0) {
×
90
            return this.thySrc();
91
        }
UNCOV
92
        return null;
×
UNCOV
93
    });
×
94

95
    /**
UNCOV
96
     * 人员名称(可设置自定义名称,需通过自定义服务 ThyAvatarService,重写 nameTransform 方法去实现转换)
×
UNCOV
97
     */
×
98
    readonly thyName = input<string>();
99

1✔
100
    readonly avatarName: Signal<string> = computed(() => {
1✔
101
        const name = this.thyAvatarService.nameTransform(this.thyName());
102
        return isString(name) ? name : this.thyName();
103
    });
104

105
    readonly avatarNameSafeHtml: Signal<SafeHtml> = computed(() => {
106
        const name = this.thyAvatarService.nameTransform(this.thyName());
107
        if (!isString(name)) {
108
            return name;
109
        }
110
        return null;
111
    });
112

113
    /**
114
     * 头像大小
115
     * @type 16 | 22 | 24 | 28 | 32 | 36 | 44 | 48 | 68 | 110 | 160 | xxs(22px) | xs(24px) | sm(32px) | md(36px) | lg(48px)
116
     * @default md
1✔
117
     */
118
    readonly thySize = model<number | string>('md');
119

120
    readonly size: Signal<number> = computed(() => {
121
        const sizeKey = this.thySize() as 'xxs' | 'xs' | 'sm' | 'md' | 'lg';
122
        if (thyAvatarSizeMap[sizeKey]) {
123
            return thyAvatarSizeMap[sizeKey];
124
        } else {
125
            const size = (this.thySize() as number) * 1;
126
            return sizeArray.indexOf(size) > -1 ? size : this.findClosestSize(sizeArray, size);
127
        }
128
    });
129

130
    /**
131
     * 已废弃,请使用 thyRemovable
132
     * @deprecated
133
     */
134
    readonly thyShowRemove = input(false, { transform: coerceBooleanProperty });
135

136
    /**
137
     * 是否展示移除按钮
138
     */
139
    readonly thyRemovable = input(false, { transform: coerceBooleanProperty });
140

141
    readonly showRemove: Signal<boolean> = computed(() => this.thyRemovable() || this.thyShowRemove());
142

143
    /**
144
     * 图片自定义类
145
     */
146
    readonly thyImgClass = input<string>();
147

148
    /**
149
     * 是否禁用
150
     */
151
    readonly thyDisabled = input(false, { transform: coerceBooleanProperty });
152

153
    /**
154
     * 图片加载策略
155
     * @type eager(立即加载) | lazy(延迟加载)
156
     */
157
    readonly thyLoading = input<ThyAvatarLoading>();
158

159
    /**
160
     * 图片加载优先级
161
     * @type auto(默认) | high(高) | low(低)
162
     */
163
    readonly thyFetchPriority = input<ThyAvatarFetchPriority>();
164

165
    private isAvatarImgError: WritableSignal<boolean> = signal(false);
166

167
    private hostRenderer = useHostRenderer();
168

169
    constructor() {
170
        effect(() => {
171
            this.hostRenderer.updateClass([`thy-avatar-${this.size()}`]);
172
        });
173
    }
174

175
    private findClosestSize(sizeArray: number[], currentSize: number): number {
176
        let closestValue = sizeArray[0];
177
        let closestDifference = Math.abs(closestValue - currentSize);
178

179
        for (let i = 1; i < sizeArray.length; i++) {
180
            const currentDifference = Math.abs(sizeArray[i] - currentSize);
181
            if (currentDifference <= closestDifference) {
182
                closestValue = sizeArray[i];
183
                closestDifference = currentDifference;
184
            }
185
        }
186

187
        return closestValue;
188
    }
189

190
    remove($event: Event) {
191
        this.thyOnRemove.emit($event);
192
        this.thyRemove.emit($event);
193
    }
194

195
    avatarImgError($event: Event) {
196
        this.isAvatarImgError.set(true);
197
        this.thyError.emit($event);
198
    }
199
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc