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

juice-shop / juice-shop / 16707578486

03 Aug 2025 05:26PM UTC coverage: 85.111% (+0.04%) from 85.067%
16707578486

push

github

J12934
Pin to typescript 5.8.x

Automatic upgrade to 5.9.x broke the build

1243 of 1674 branches covered (74.25%)

Branch coverage included in aggregate %.

5131 of 5815 relevant lines covered (88.24%)

41.0 hits per line

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

87.69
/frontend/src/app/code-snippet/code-snippet.component.ts
1
/*
2
 * Copyright (c) 2014-2025 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, Inject, type OnInit } 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 { ConfigurationService } from '../Services/configuration.service'
16
import { type ThemePalette } from '@angular/material/core'
17
import { MatIconButton, MatButtonModule } from '@angular/material/button'
18
import { MatInputModule } from '@angular/material/input'
19
import { MatFormFieldModule, MatLabel } from '@angular/material/form-field'
20
import { ExtendedModule } from '@angular/flex-layout/extended'
21
import { MatCardModule } from '@angular/material/card'
22
import { CodeFixesComponent } from '../code-fixes/code-fixes.component'
23
import { MatIconModule } from '@angular/material/icon'
24
import { TranslateModule } from '@ngx-translate/core'
25
import { CodeAreaComponent } from '../code-area/code-area.component'
26
import { NgIf, NgFor } from '@angular/common'
27
import { FlexModule } from '@angular/flex-layout/flex'
28
import { MatTabGroup, MatTab, MatTabLabel } from '@angular/material/tabs'
29

30
enum ResultState {
1✔
31
  Undecided,
1✔
32
  Right,
1✔
33
  Wrong,
1✔
34
}
35

36
export interface Solved {
37
  findIt: boolean
38
  fixIt: boolean
39
}
40

41
export interface RandomFixes {
42
  fix: string
43
  index: number
44
}
45

46
@Component({
47
  selector: 'code-snippet',
48
  templateUrl: './code-snippet.component.html',
49
  styleUrls: ['./code-snippet.component.scss'],
50
  host: { class: 'code-snippet' },
51
  imports: [MatDialogTitle, MatDialogContent, MatTabGroup, MatTab, FlexModule, NgIf, CodeAreaComponent, TranslateModule, MatTabLabel, MatIconModule, CodeFixesComponent, MatDialogActions, MatCardModule, ExtendedModule, MatFormFieldModule, MatLabel, MatInputModule, NgFor, FormsModule, MatIconButton, MatButtonModule, MatDialogClose]
52
})
53
export class CodeSnippetComponent implements OnInit {
1✔
54
  public snippet: CodeSnippet = null
20✔
55
  public fixes: string [] = null
20✔
56
  public selectedLines: number[]
57
  public selectedFix: number = 0
20✔
58
  public tab: UntypedFormControl = new UntypedFormControl(0)
20✔
59
  public lock: ResultState = ResultState.Undecided
20✔
60
  public result: ResultState = ResultState.Undecided
20✔
61
  public hint: string = null
20✔
62
  public explanation: string = null
20✔
63
  public solved: Solved = { findIt: false, fixIt: false }
20✔
64
  public showFeedbackButtons: boolean = true
20✔
65
  public randomFixes: RandomFixes[] = []
20✔
66

67
  constructor (@Inject(MAT_DIALOG_DATA) public dialogData: any, private readonly configurationService: ConfigurationService, private readonly codeSnippetService: CodeSnippetService, private readonly vulnLinesService: VulnLinesService, private readonly codeFixesService: CodeFixesService, private readonly challengeService: ChallengeService, private readonly cookieService: CookieService) { }
20✔
68

69
  ngOnInit (): void {
70
    this.configurationService.getApplicationConfiguration().subscribe({
26✔
71
      next: (config) => {
72
        this.showFeedbackButtons = config.challenges.showFeedbackButtons
25✔
73
      },
74
      error: (err) => { console.log(err) }
1✔
75
    })
76

77
    this.codeSnippetService.get(this.dialogData.key).subscribe({
26✔
78
      next: (snippet) => {
79
        this.snippet = snippet
25✔
80
        this.solved.findIt = false
25✔
81
        if (this.dialogData.codingChallengeStatus >= 1) {
25✔
82
          this.result = ResultState.Right
1✔
83
          this.lock = ResultState.Right
1✔
84
          this.solved.findIt = true
1✔
85
        }
86
      },
87
      error: (err) => {
88
        this.snippet = { snippet: err.error }
1✔
89
      }
90
    })
91
    this.codeFixesService.get(this.dialogData.key).subscribe({
26✔
92
      next: (fixes) => {
93
        this.fixes = fixes.fixes
25✔
94
        if (this.fixes) {
25!
95
          this.shuffle()
×
96
        }
97
        this.solved.fixIt = this.dialogData.codingChallengeStatus >= 2
25✔
98
      },
99
      error: () => {
100
        this.fixes = null
1✔
101
      }
102
    })
103
  }
104

105
  addLine = (lines: number[]) => {
20✔
106
    this.selectedLines = lines
1✔
107
  }
108

109
  setFix = (fix: number) => {
20✔
110
    this.selectedFix = fix
2✔
111
    this.explanation = null
2✔
112
  }
113

114
  changeFix (event: Event) {
115
    this.setFix(parseInt((event.target as HTMLSelectElement).value, 10))
×
116
  }
117

118
  toggleTab = (event: number) => {
20✔
119
    this.tab.setValue(event)
1✔
120
    this.result = ResultState.Undecided
1✔
121
    if (event === 0) {
1!
122
      if (this.solved.findIt) this.result = ResultState.Right
×
123
    }
124
    if (event === 1) {
1✔
125
      if (this.solved.fixIt) this.result = ResultState.Right
1!
126
    }
127
  }
128

129
  checkFix = () => {
20✔
130
    this.codeFixesService.check(this.dialogData.key, this.randomFixes[this.selectedFix].index).subscribe((verdict) => {
1✔
131
      this.setVerdict(verdict.verdict)
1✔
132
      this.explanation = verdict.explanation
1✔
133
    })
134
  }
135

136
  checkLines = () => {
20✔
137
    this.vulnLinesService.check(this.dialogData.key, this.selectedLines).subscribe((verdict: result) => {
1✔
138
      this.setVerdict(verdict.verdict)
1✔
139
      this.hint = verdict.hint
1✔
140
    })
141
  }
142

143
  lockIcon (): string {
144
    if (this.fixes === null) {
208✔
145
      return 'lock'
2✔
146
    }
147
    switch (this.lock) {
206✔
148
      case ResultState.Right:
149
        return 'lock_open'
2✔
150
      case ResultState.Wrong:
151
        return 'lock'
2✔
152
      case ResultState.Undecided:
153
        return 'lock'
202✔
154
    }
155
  }
156

157
  lockColor (): ThemePalette {
158
    switch (this.lockIcon()) {
84✔
159
      case 'lock_open':
160
        return 'accent'
1✔
161
      case 'lock':
162
        return 'warn'
83✔
163
    }
164
  }
165

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

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

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

227
  resultColor (): ThemePalette {
228
    switch (this.resultIcon()) {
42✔
229
      case 'check':
230
        return 'accent'
1✔
231
      case 'clear':
232
        return 'warn'
1✔
233
    }
234
  }
235
}
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

© 2026 Coveralls, Inc