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

juice-shop / juice-shop / 20272126283

16 Dec 2025 12:20PM UTC coverage: 86.917% (+0.2%) from 86.703%
20272126283

push

github

bkimminich
Adjust test to changes in lock behavior and removing feedback

1293 of 1695 branches covered (76.28%)

Branch coverage included in aggregate %.

5397 of 6002 relevant lines covered (89.92%)

41.29 hits per line

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

87.04
/frontend/src/app/code-snippet/code-snippet.component.ts
1
/*
2
 * Copyright (c) 2014-2026 Bjoern Kimminich & the OWASP Juice Shop contributors.
3
 * SPDX-License-Identifier: MIT
4
 */
5

6
import { CodeSnippetService, type CodeSnippet } from '../Services/code-snippet.service'
7
import { CodeFixesService } from '../Services/code-fixes.service'
8
import { CookieService } from 'ngy-cookie'
9
import { ChallengeService } from '../Services/challenge.service'
10
import { VulnLinesService, type result } from '../Services/vuln-lines.service'
11
import { Component, type OnInit, inject } from '@angular/core'
12

13
import { MAT_DIALOG_DATA, MatDialogTitle, MatDialogContent, MatDialogActions, MatDialogClose } from '@angular/material/dialog'
14
import { UntypedFormControl, FormsModule } from '@angular/forms'
15
import { type ThemePalette } from '@angular/material/core'
16
import { MatButtonModule } from '@angular/material/button'
17
import { MatInputModule } from '@angular/material/input'
18
import { MatFormFieldModule, MatLabel } from '@angular/material/form-field'
19

20
import { MatCardModule } from '@angular/material/card'
21
import { CodeFixesComponent } from '../code-fixes/code-fixes.component'
22
import { MatIconModule } from '@angular/material/icon'
23
import { TranslateModule } from '@ngx-translate/core'
24
import { CodeAreaComponent } from '../code-area/code-area.component'
25

26
import { MatTabGroup, MatTab, MatTabLabel } from '@angular/material/tabs'
27

28
enum ResultState {
1✔
29
  Undecided,
1✔
30
  Right,
1✔
31
  Wrong,
1✔
32
}
33

34
export interface Solved {
35
  findIt: boolean
36
  fixIt: boolean
37
}
38

39
export interface RandomFixes {
40
  fix: string
41
  index: number
42
}
43

44
@Component({
45
  selector: 'code-snippet',
46
  templateUrl: './code-snippet.component.html',
47
  styleUrls: ['./code-snippet.component.scss'],
48
  host: { class: 'code-snippet' },
49
  imports: [MatDialogTitle, MatDialogContent, MatTabGroup, MatTab, CodeAreaComponent, TranslateModule, MatTabLabel, MatIconModule, CodeFixesComponent, MatDialogActions, MatCardModule, MatFormFieldModule, MatLabel, MatInputModule, FormsModule, MatButtonModule, MatDialogClose]
50
})
51
export class CodeSnippetComponent implements OnInit {
1✔
52
  dialogData = inject(MAT_DIALOG_DATA)
19✔
53
  private readonly codeSnippetService = inject(CodeSnippetService)
19✔
54
  private readonly vulnLinesService = inject(VulnLinesService)
19✔
55
  private readonly codeFixesService = inject(CodeFixesService)
19✔
56
  private readonly challengeService = inject(ChallengeService)
19✔
57
  private readonly cookieService = inject(CookieService)
19✔
58

59
  public snippet: CodeSnippet = null
19✔
60
  public fixes: string [] = null
19✔
61
  public selectedLines: number[]
62
  public selectedFix = 0
19✔
63
  public tab: UntypedFormControl = new UntypedFormControl(0)
19✔
64
  public result: ResultState = ResultState.Undecided
19✔
65
  public hint: string = null
19✔
66
  public explanation: string = null
19✔
67
  public solved: Solved = { findIt: false, fixIt: false }
19✔
68
  public randomFixes: RandomFixes[] = []
19✔
69
  private snippetLoaded = false
19✔
70
  private fixesLoaded = false
19✔
71
  private initialTabSet = false
19✔
72

73
  ngOnInit (): void {
74
    this.codeSnippetService.get(this.dialogData.key).subscribe({
24✔
75
      next: (snippet) => {
76
        this.snippet = snippet
23✔
77
        this.solved.findIt = false
23✔
78
        if (this.dialogData.codingChallengeStatus >= 1) {
23✔
79
          this.result = ResultState.Right
1✔
80
          this.solved.findIt = true
1✔
81
        }
82
        this.snippetLoaded = true
23✔
83
        this.setInitialTabIfReady()
23✔
84
      },
85
      error: (err) => {
86
        this.snippet = { snippet: err.error }
1✔
87
        this.snippetLoaded = true
1✔
88
        this.setInitialTabIfReady()
1✔
89
      }
90
    })
91
    this.codeFixesService.get(this.dialogData.key).subscribe({
24✔
92
      next: (fixes) => {
93
        this.fixes = fixes.fixes
23✔
94
        if (this.fixes) {
23!
95
          this.shuffle()
×
96
        }
97
        this.solved.fixIt = this.dialogData.codingChallengeStatus >= 2
23✔
98
        this.fixesLoaded = true
23✔
99
        this.setInitialTabIfReady()
23✔
100
      },
101
      error: () => {
102
        this.fixes = null
1✔
103
        this.fixesLoaded = true
1✔
104
        this.setInitialTabIfReady()
1✔
105
      }
106
    })
107
  }
108

109
  addLine = (lines: number[]) => {
19✔
110
    this.selectedLines = lines
1✔
111
  }
112

113
  setFix = (fix: number) => {
19✔
114
    this.selectedFix = fix
2✔
115
    this.explanation = null
2✔
116
  }
117

118
  changeFix (event: Event) {
119
    this.setFix(parseInt((event.target as HTMLSelectElement).value, 10))
×
120
  }
121

122
  toggleTab = (event: number) => {
19✔
123
    this.tab.setValue(event)
20✔
124
    this.result = ResultState.Undecided
20✔
125
    if (event === 0) {
20✔
126
      if (this.solved.findIt) this.result = ResultState.Right
19!
127
    }
128
    if (event === 1) {
20✔
129
      if (this.solved.fixIt) this.result = ResultState.Right
1!
130
    }
131
  }
132

133
  checkFix = () => {
19✔
134
    this.codeFixesService.check(this.dialogData.key, this.randomFixes[this.selectedFix].index).subscribe((verdict) => {
1✔
135
      this.setVerdict(verdict.verdict)
1✔
136
      this.explanation = verdict.explanation
1✔
137
    })
138
  }
139

140
  checkLines = () => {
19✔
141
    this.vulnLinesService.check(this.dialogData.key, this.selectedLines).subscribe((verdict: result) => {
1✔
142
      this.setVerdict(verdict.verdict)
1✔
143
      this.hint = verdict.hint
1✔
144
    })
145
  }
146

147
  findLockIcon (): string {
148
    return this.solved.findIt ? 'lock_open' : 'lock'
43✔
149
  }
150

151
  findLockColor (): ThemePalette {
152
    return this.solved.findIt ? 'accent' as ThemePalette : 'warn' as ThemePalette
43✔
153
  }
154

155
  fixLockIcon (): string {
156
    if (this.fixes === null) return 'lock'
42✔
157
    return this.solved.fixIt ? 'lock_open' : 'lock'
41!
158
  }
159

160
  fixLockColor (): ThemePalette {
161
    if (this.fixes === null) return 'warn' as ThemePalette
42✔
162
    return this.solved.fixIt ? 'accent' as ThemePalette : 'warn' as ThemePalette
41!
163
  }
164

165
  shuffle () {
166
    this.randomFixes = this.fixes
1✔
167
      .map((fix, index) => ({ fix, index, sort: Math.random() }))
3✔
168
      .sort((a, b) => a.sort - b.sort)
2✔
169
      .map(({ fix, index }) => ({ fix, index }))
3✔
170
  }
171

172
  setVerdict = (verdict: boolean) => {
19✔
173
    if (this.result === ResultState.Right) return
2!
174
    if (verdict) {
2!
175
      if (this.tab.value === 0) {
2✔
176
        this.solved.findIt = true
1✔
177
        this.challengeService.continueCodeFindIt().subscribe({
1✔
178
          next: (continueCode) => {
179
            if (!continueCode) {
1!
180
              throw (new Error('Received invalid continue code from the server!'))
×
181
            }
182
            const expires = new Date()
1✔
183
            expires.setFullYear(expires.getFullYear() + 1)
1✔
184
            this.cookieService.put('continueCodeFindIt', continueCode, { expires })
1✔
185
          },
186
          error: (err) => { console.log(err) }
×
187
        })
188
      } else {
189
        this.solved.fixIt = true
1✔
190
        this.challengeService.continueCodeFixIt().subscribe({
1✔
191
          next: (continueCode) => {
192
            if (!continueCode) {
1!
193
              throw (new Error('Received invalid continue code from the server!'))
×
194
            }
195
            const expires = new Date()
1✔
196
            expires.setFullYear(expires.getFullYear() + 1)
1✔
197
            this.cookieService.put('continueCodeFixIt', continueCode, { expires })
1✔
198
          },
199
          error: (err) => { console.log(err) }
×
200
        })
201
      }
202
      this.result = ResultState.Right
2✔
203
      import('../../confetti').then(module => {
2✔
204
        module.shootConfetti()
2✔
205
      })
206
        .then(() => {
207
          if (this.tab.value === 0 && this.fixes !== null) this.toggleTab(1)
2✔
208
        })
209
    } else {
210
      this.result = ResultState.Wrong
×
211
    }
212
  }
213

214
  resultIcon (): string {
215
    switch (this.result) {
81✔
216
      case ResultState.Right:
217
        return 'check'
2✔
218
      case ResultState.Wrong:
219
        return 'clear'
2✔
220
      default:
221
        return 'send'
77✔
222
    }
223
  }
224

225
  resultColor (): ThemePalette {
226
    switch (this.resultIcon()) {
40✔
227
      case 'check':
228
        return 'accent'
1✔
229
      case 'clear':
230
        return 'warn'
1✔
231
    }
232
  }
233

234
  private setInitialTabIfReady (): void {
235
    if (this.initialTabSet) return
48✔
236
    if (!this.snippetLoaded || !this.fixesLoaded) return
38✔
237

238
    const status = this.dialogData.codingChallengeStatus
19✔
239
    let initialIndex = 0
19✔
240
    if (status >= 2) {
19!
241
      initialIndex = 0
×
242
    } else if (status >= 1 && this.fixes !== null) {
19!
243
      initialIndex = 1
×
244
    } else {
245
      initialIndex = 0
19✔
246
    }
247

248
    this.tab.setValue(initialIndex)
19✔
249
    this.toggleTab(initialIndex)
19✔
250
    this.initialTabSet = true
19✔
251
  }
252
}
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