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

IgniteUI / igniteui-angular / 13331632524

14 Feb 2025 02:51PM CUT coverage: 22.015% (-69.6%) from 91.622%
13331632524

Pull #15372

github

web-flow
Merge d52d57714 into bcb78ae0a
Pull Request #15372: chore(*): test ci passing

1990 of 15592 branches covered (12.76%)

431 of 964 new or added lines in 18 files covered. (44.71%)

19956 existing lines in 307 files now uncovered.

6452 of 29307 relevant lines covered (22.02%)

249.17 hits per line

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

17.86
/projects/igniteui-angular/src/lib/directives/mask/mask-parsing.service.ts
1
import { Injectable } from '@angular/core';
2

3

4
const FLAGS = new Set('aACL09#&?');
2✔
5
const REGEX = new Map([
2✔
6
    ['C', /(?!^$)/u], // Non-empty
7
    ['&', /[^\p{Separator}]/u], // Non-whitespace
8
    ['a', /[\p{Letter}\d\p{Separator}]/u], // Alphanumeric & whitespace
9
    ['A', /[\p{Letter}\d]/u], // Alphanumeric
10
    ['?', /[\p{Letter}\p{Separator}]/u], // Alpha & whitespace
11
    ['L', /\p{Letter}/u], // Alpha
12
    ['0', /\d/], // Numeric
13
    ['9', /[\d\p{Separator}]/u], // Numeric & whitespace
14
    ['#', /[\d\-+]/], // Numeric and sign
15
]);
16

17
/** @hidden */
18
export interface MaskOptions {
19
    format: string;
20
    promptChar: string;
21
}
22

23
/** @hidden */
24
export interface Replaced {
25
    value: string;
26
    end: number;
27
}
28

29
interface ParsedMask {
30
    literals: Map<number, string>,
31
    mask: string
32
}
33

34
const replaceCharAt = (string: string, idx: number, char: string) =>
2✔
UNCOV
35
    `${string.substring(0, idx)}${char}${string.substring(idx + 1)}`;
×
36

37

38
export function parseMask(format: string): ParsedMask {
39
    const literals = new Map<number, string>();
4✔
40
    let mask = format;
4✔
41

42
    for (let i = 0, j = 0; i < format.length; i++, j++) {
4✔
43
        const [current, next] = [format.charAt(i), format.charAt(i + 1)];
40✔
44

45
        if (current === '\\' && FLAGS.has(next)) {
40!
UNCOV
46
            mask = replaceCharAt(mask, j, '');
×
UNCOV
47
            literals.set(j, next);
×
UNCOV
48
            i++;
×
49
        } else {
50
            if (!FLAGS.has(current)) {
40!
UNCOV
51
                literals.set(j, current);
×
52
            }
53
        }
54
    }
55

56
    return { literals, mask };
4✔
57
}
58

59
/** @hidden */
60
@Injectable({
61
    providedIn: 'root'
62
})
63
export class MaskParsingService {
2✔
64

65
    public applyMask(inputVal: string, maskOptions: MaskOptions, pos = 0): string {
×
UNCOV
66
        let outputVal = '';
×
UNCOV
67
        let value = '';
×
UNCOV
68
        const { literals, mask } = parseMask(maskOptions.format);
×
UNCOV
69
        const literalKeys: number[] = Array.from(literals.keys());
×
UNCOV
70
        const nonLiteralIndices: number[] = this.getNonLiteralIndices(mask, literalKeys);
×
UNCOV
71
        const literalValues: string[] = Array.from(literals.values());
×
72

UNCOV
73
        if (inputVal != null) {
×
UNCOV
74
            value = inputVal.toString();
×
75
        }
76

UNCOV
77
        for (const _maskSym of mask) {
×
UNCOV
78
            outputVal += maskOptions.promptChar;
×
79
        }
80

UNCOV
81
        literals.forEach((val: string, key: number) => {
×
UNCOV
82
            outputVal = replaceCharAt(outputVal, key, val);
×
83
        });
84

UNCOV
85
        if (!value) {
×
UNCOV
86
            return outputVal;
×
87
        }
88

UNCOV
89
        const nonLiteralValues: string[] = this.getNonLiteralValues(value, literalValues);
×
90

UNCOV
91
        for (let i = 0; i < nonLiteralValues.length; i++) {
×
UNCOV
92
            const char = nonLiteralValues[i];
×
UNCOV
93
            const isCharValid = this.validateCharOnPosition(char, nonLiteralIndices[i], mask);
×
94

UNCOV
95
            if (!isCharValid && char !== maskOptions.promptChar) {
×
UNCOV
96
                nonLiteralValues[i] = maskOptions.promptChar;
×
97
            }
98
        }
99

UNCOV
100
        if (nonLiteralValues.length > nonLiteralIndices.length) {
×
UNCOV
101
            nonLiteralValues.splice(nonLiteralIndices.length);
×
102
        }
103

UNCOV
104
        for (const nonLiteralValue of nonLiteralValues) {
×
UNCOV
105
            const char = nonLiteralValue;
×
UNCOV
106
            outputVal = replaceCharAt(outputVal, nonLiteralIndices[pos++], char);
×
107
        }
108

UNCOV
109
        return outputVal;
×
110
    }
111

112
    public parseValueFromMask(maskedValue: string, maskOptions: MaskOptions): string {
113
        let outputVal = '';
4✔
114
        const literalValues: string[] = Array.from(parseMask(maskOptions.format).literals.values());
4✔
115

116
        for (const val of maskedValue) {
4✔
UNCOV
117
            if (literalValues.indexOf(val) === -1) {
×
UNCOV
118
                if (val !== maskOptions.promptChar) {
×
UNCOV
119
                    outputVal += val;
×
120
                }
121
            }
122
        }
123

124
        return outputVal;
4✔
125
    }
126

127
    public replaceInMask(maskedValue: string, value: string, maskOptions: MaskOptions, start: number, end: number): Replaced {
UNCOV
128
        const { literals, mask } = parseMask(maskOptions.format);
×
UNCOV
129
        const literalsPositions = Array.from(literals.keys());
×
UNCOV
130
        value = this.replaceIMENumbers(value);
×
UNCOV
131
        const chars = Array.from(value);
×
UNCOV
132
        let cursor = start;
×
UNCOV
133
        end = Math.min(end, maskedValue.length);
×
134

UNCOV
135
        for (let i = start; i < end || (chars.length && i < maskedValue.length); i++) {
×
UNCOV
136
            if (literalsPositions.indexOf(i) !== -1) {
×
UNCOV
137
                if (chars[0] === maskedValue[i] || value.length < 1) {
×
UNCOV
138
                    cursor = i + 1;
×
UNCOV
139
                    chars.shift();
×
140
                }
UNCOV
141
                continue;
×
142
            }
UNCOV
143
            if (chars[0]
×
144
                && !this.validateCharOnPosition(chars[0], i, mask)
145
                && chars[0] !== maskOptions.promptChar) {
UNCOV
146
                break;
×
147
            }
148

UNCOV
149
            let char = maskOptions.promptChar;
×
UNCOV
150
            if (chars.length) {
×
UNCOV
151
                cursor = i + 1;
×
UNCOV
152
                char = chars.shift();
×
153
            }
UNCOV
154
            if (value.length < 1) {
×
155
                // on `delete` the cursor should move forward
UNCOV
156
                cursor++;
×
157
            }
UNCOV
158
            maskedValue = replaceCharAt(maskedValue, i, char);
×
159
        }
160

UNCOV
161
        return { value: maskedValue, end: cursor };
×
162
    }
163

164
    /** Validates only non literal positions. */
165
    private validateCharOnPosition(inputChar: string, position: number, mask: string): boolean {
UNCOV
166
        const regex = REGEX.get(mask.charAt(position));
×
UNCOV
167
        return regex ? regex.test(inputChar) : false;
×
168
    }
169

170
    private getNonLiteralIndices(mask: string, literalKeys: number[]): number[] {
UNCOV
171
        const nonLiteralsIndices: number[] = [];
×
172

UNCOV
173
        for (let i = 0; i < mask.length; i++) {
×
UNCOV
174
            if (literalKeys.indexOf(i) === -1) {
×
UNCOV
175
                nonLiteralsIndices.push(i);
×
176
            }
177
        }
178

UNCOV
179
        return nonLiteralsIndices;
×
180
    }
181
    private getNonLiteralValues(value: string, literalValues: string[]): string[] {
UNCOV
182
        const nonLiteralValues: string[] = [];
×
183

UNCOV
184
        for (const val of value) {
×
UNCOV
185
            if (literalValues.indexOf(val) === -1) {
×
UNCOV
186
                nonLiteralValues.push(val);
×
187
            }
188
        }
189

UNCOV
190
        return nonLiteralValues;
×
191
    }
192

193
    private replaceIMENumbers(value: string): string {
UNCOV
194
        return value.replace(/[0123456789]/g, (num) => ({
×
195
            '1': '1', '2': '2', '3': '3', '4': '4', '5': '5',
196
            '6': '6', '7': '7', '8': '8', '9': '9', '0': '0'
197
        }[num]));
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