chore: 생성물 frontend/coverage/ 추적 제외 (.gitignore)
커버리지 리포트는 빌드 산출물이라 저장소에 두지 않는다(직전 커밋에서 실수로 포함). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
6ba8776e04
commit
63f34aa025
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,6 +2,7 @@
|
|||||||
backend/target/
|
backend/target/
|
||||||
frontend/dist/
|
frontend/dist/
|
||||||
frontend/node_modules/
|
frontend/node_modules/
|
||||||
|
frontend/coverage/
|
||||||
|
|
||||||
# 비밀값 — 절대 커밋 금지
|
# 비밀값 — 절대 커밋 금지
|
||||||
deploy/.env
|
deploy/.env
|
||||||
|
|||||||
@ -1,224 +0,0 @@
|
|||||||
body, html {
|
|
||||||
margin:0; padding: 0;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
font-family: Helvetica Neue, Helvetica, Arial;
|
|
||||||
font-size: 14px;
|
|
||||||
color:#333;
|
|
||||||
}
|
|
||||||
.small { font-size: 12px; }
|
|
||||||
*, *:after, *:before {
|
|
||||||
-webkit-box-sizing:border-box;
|
|
||||||
-moz-box-sizing:border-box;
|
|
||||||
box-sizing:border-box;
|
|
||||||
}
|
|
||||||
h1 { font-size: 20px; margin: 0;}
|
|
||||||
h2 { font-size: 14px; }
|
|
||||||
pre {
|
|
||||||
font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
-moz-tab-size: 2;
|
|
||||||
-o-tab-size: 2;
|
|
||||||
tab-size: 2;
|
|
||||||
}
|
|
||||||
a { color:#0074D9; text-decoration:none; }
|
|
||||||
a:hover { text-decoration:underline; }
|
|
||||||
.strong { font-weight: bold; }
|
|
||||||
.space-top1 { padding: 10px 0 0 0; }
|
|
||||||
.pad2y { padding: 20px 0; }
|
|
||||||
.pad1y { padding: 10px 0; }
|
|
||||||
.pad2x { padding: 0 20px; }
|
|
||||||
.pad2 { padding: 20px; }
|
|
||||||
.pad1 { padding: 10px; }
|
|
||||||
.space-left2 { padding-left:55px; }
|
|
||||||
.space-right2 { padding-right:20px; }
|
|
||||||
.center { text-align:center; }
|
|
||||||
.clearfix { display:block; }
|
|
||||||
.clearfix:after {
|
|
||||||
content:'';
|
|
||||||
display:block;
|
|
||||||
height:0;
|
|
||||||
clear:both;
|
|
||||||
visibility:hidden;
|
|
||||||
}
|
|
||||||
.fl { float: left; }
|
|
||||||
@media only screen and (max-width:640px) {
|
|
||||||
.col3 { width:100%; max-width:100%; }
|
|
||||||
.hide-mobile { display:none!important; }
|
|
||||||
}
|
|
||||||
|
|
||||||
.quiet {
|
|
||||||
color: #7f7f7f;
|
|
||||||
color: rgba(0,0,0,0.5);
|
|
||||||
}
|
|
||||||
.quiet a { opacity: 0.7; }
|
|
||||||
|
|
||||||
.fraction {
|
|
||||||
font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
|
|
||||||
font-size: 10px;
|
|
||||||
color: #555;
|
|
||||||
background: #E8E8E8;
|
|
||||||
padding: 4px 5px;
|
|
||||||
border-radius: 3px;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.path a:link, div.path a:visited { color: #333; }
|
|
||||||
table.coverage {
|
|
||||||
border-collapse: collapse;
|
|
||||||
margin: 10px 0 0 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.coverage td {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
table.coverage td.line-count {
|
|
||||||
text-align: right;
|
|
||||||
padding: 0 5px 0 20px;
|
|
||||||
}
|
|
||||||
table.coverage td.line-coverage {
|
|
||||||
text-align: right;
|
|
||||||
padding-right: 10px;
|
|
||||||
min-width:20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.coverage td span.cline-any {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0 5px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.missing-if-branch {
|
|
||||||
display: inline-block;
|
|
||||||
margin-right: 5px;
|
|
||||||
border-radius: 3px;
|
|
||||||
position: relative;
|
|
||||||
padding: 0 4px;
|
|
||||||
background: #333;
|
|
||||||
color: yellow;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skip-if-branch {
|
|
||||||
display: none;
|
|
||||||
margin-right: 10px;
|
|
||||||
position: relative;
|
|
||||||
padding: 0 4px;
|
|
||||||
background: #ccc;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.missing-if-branch .typ, .skip-if-branch .typ {
|
|
||||||
color: inherit !important;
|
|
||||||
}
|
|
||||||
.coverage-summary {
|
|
||||||
border-collapse: collapse;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.coverage-summary tr { border-bottom: 1px solid #bbb; }
|
|
||||||
.keyline-all { border: 1px solid #ddd; }
|
|
||||||
.coverage-summary td, .coverage-summary th { padding: 10px; }
|
|
||||||
.coverage-summary tbody { border: 1px solid #bbb; }
|
|
||||||
.coverage-summary td { border-right: 1px solid #bbb; }
|
|
||||||
.coverage-summary td:last-child { border-right: none; }
|
|
||||||
.coverage-summary th {
|
|
||||||
text-align: left;
|
|
||||||
font-weight: normal;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
.coverage-summary th.file { border-right: none !important; }
|
|
||||||
.coverage-summary th.pct { }
|
|
||||||
.coverage-summary th.pic,
|
|
||||||
.coverage-summary th.abs,
|
|
||||||
.coverage-summary td.pct,
|
|
||||||
.coverage-summary td.abs { text-align: right; }
|
|
||||||
.coverage-summary td.file { white-space: nowrap; }
|
|
||||||
.coverage-summary td.pic { min-width: 120px !important; }
|
|
||||||
.coverage-summary tfoot td { }
|
|
||||||
|
|
||||||
.coverage-summary .sorter {
|
|
||||||
height: 10px;
|
|
||||||
width: 7px;
|
|
||||||
display: inline-block;
|
|
||||||
margin-left: 0.5em;
|
|
||||||
background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent;
|
|
||||||
}
|
|
||||||
.coverage-summary .sorted .sorter {
|
|
||||||
background-position: 0 -20px;
|
|
||||||
}
|
|
||||||
.coverage-summary .sorted-desc .sorter {
|
|
||||||
background-position: 0 -10px;
|
|
||||||
}
|
|
||||||
.status-line { height: 10px; }
|
|
||||||
/* yellow */
|
|
||||||
.cbranch-no { background: yellow !important; color: #111; }
|
|
||||||
/* dark red */
|
|
||||||
.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 }
|
|
||||||
.low .chart { border:1px solid #C21F39 }
|
|
||||||
.highlighted,
|
|
||||||
.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{
|
|
||||||
background: #C21F39 !important;
|
|
||||||
}
|
|
||||||
/* medium red */
|
|
||||||
.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE }
|
|
||||||
/* light red */
|
|
||||||
.low, .cline-no { background:#FCE1E5 }
|
|
||||||
/* light green */
|
|
||||||
.high, .cline-yes { background:rgb(230,245,208) }
|
|
||||||
/* medium green */
|
|
||||||
.cstat-yes { background:rgb(161,215,106) }
|
|
||||||
/* dark green */
|
|
||||||
.status-line.high, .high .cover-fill { background:rgb(77,146,33) }
|
|
||||||
.high .chart { border:1px solid rgb(77,146,33) }
|
|
||||||
/* dark yellow (gold) */
|
|
||||||
.status-line.medium, .medium .cover-fill { background: #f9cd0b; }
|
|
||||||
.medium .chart { border:1px solid #f9cd0b; }
|
|
||||||
/* light yellow */
|
|
||||||
.medium { background: #fff4c2; }
|
|
||||||
|
|
||||||
.cstat-skip { background: #ddd; color: #111; }
|
|
||||||
.fstat-skip { background: #ddd; color: #111 !important; }
|
|
||||||
.cbranch-skip { background: #ddd !important; color: #111; }
|
|
||||||
|
|
||||||
span.cline-neutral { background: #eaeaea; }
|
|
||||||
|
|
||||||
.coverage-summary td.empty {
|
|
||||||
opacity: .5;
|
|
||||||
padding-top: 4px;
|
|
||||||
padding-bottom: 4px;
|
|
||||||
line-height: 1;
|
|
||||||
color: #888;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cover-fill, .cover-empty {
|
|
||||||
display:inline-block;
|
|
||||||
height: 12px;
|
|
||||||
}
|
|
||||||
.chart {
|
|
||||||
line-height: 0;
|
|
||||||
}
|
|
||||||
.cover-empty {
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
.cover-full {
|
|
||||||
border-right: none !important;
|
|
||||||
}
|
|
||||||
pre.prettyprint {
|
|
||||||
border: none !important;
|
|
||||||
padding: 0 !important;
|
|
||||||
margin: 0 !important;
|
|
||||||
}
|
|
||||||
.com { color: #999 !important; }
|
|
||||||
.ignore-none { color: #999; font-weight: normal; }
|
|
||||||
|
|
||||||
.wrapper {
|
|
||||||
min-height: 100%;
|
|
||||||
height: auto !important;
|
|
||||||
height: 100%;
|
|
||||||
margin: 0 auto -48px;
|
|
||||||
}
|
|
||||||
.footer, .push {
|
|
||||||
height: 48px;
|
|
||||||
}
|
|
||||||
@ -1,87 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
var jumpToCode = (function init() {
|
|
||||||
// Classes of code we would like to highlight in the file view
|
|
||||||
var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no'];
|
|
||||||
|
|
||||||
// Elements to highlight in the file listing view
|
|
||||||
var fileListingElements = ['td.pct.low'];
|
|
||||||
|
|
||||||
// We don't want to select elements that are direct descendants of another match
|
|
||||||
var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > `
|
|
||||||
|
|
||||||
// Selector that finds elements on the page to which we can jump
|
|
||||||
var selector =
|
|
||||||
fileListingElements.join(', ') +
|
|
||||||
', ' +
|
|
||||||
notSelector +
|
|
||||||
missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b`
|
|
||||||
|
|
||||||
// The NodeList of matching elements
|
|
||||||
var missingCoverageElements = document.querySelectorAll(selector);
|
|
||||||
|
|
||||||
var currentIndex;
|
|
||||||
|
|
||||||
function toggleClass(index) {
|
|
||||||
missingCoverageElements
|
|
||||||
.item(currentIndex)
|
|
||||||
.classList.remove('highlighted');
|
|
||||||
missingCoverageElements.item(index).classList.add('highlighted');
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeCurrent(index) {
|
|
||||||
toggleClass(index);
|
|
||||||
currentIndex = index;
|
|
||||||
missingCoverageElements.item(index).scrollIntoView({
|
|
||||||
behavior: 'smooth',
|
|
||||||
block: 'center',
|
|
||||||
inline: 'center'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function goToPrevious() {
|
|
||||||
var nextIndex = 0;
|
|
||||||
if (typeof currentIndex !== 'number' || currentIndex === 0) {
|
|
||||||
nextIndex = missingCoverageElements.length - 1;
|
|
||||||
} else if (missingCoverageElements.length > 1) {
|
|
||||||
nextIndex = currentIndex - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
makeCurrent(nextIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
function goToNext() {
|
|
||||||
var nextIndex = 0;
|
|
||||||
|
|
||||||
if (
|
|
||||||
typeof currentIndex === 'number' &&
|
|
||||||
currentIndex < missingCoverageElements.length - 1
|
|
||||||
) {
|
|
||||||
nextIndex = currentIndex + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
makeCurrent(nextIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return function jump(event) {
|
|
||||||
if (
|
|
||||||
document.getElementById('fileSearch') === document.activeElement &&
|
|
||||||
document.activeElement != null
|
|
||||||
) {
|
|
||||||
// if we're currently focused on the search input, we don't want to navigate
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (event.which) {
|
|
||||||
case 78: // n
|
|
||||||
case 74: // j
|
|
||||||
goToNext();
|
|
||||||
break;
|
|
||||||
case 66: // b
|
|
||||||
case 75: // k
|
|
||||||
case 80: // p
|
|
||||||
goToPrevious();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
window.addEventListener('keydown', jumpToCode);
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 445 B |
@ -1,403 +0,0 @@
|
|||||||
|
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<title>Code coverage report for glossary.js</title>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<link rel="stylesheet" href="prettify.css" />
|
|
||||||
<link rel="stylesheet" href="base.css" />
|
|
||||||
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<style type='text/css'>
|
|
||||||
.coverage-summary .sorter {
|
|
||||||
background-image: url(sort-arrow-sprite.png);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div class='wrapper'>
|
|
||||||
<div class='pad1'>
|
|
||||||
<h1><a href="index.html">All files</a> glossary.js</h1>
|
|
||||||
<div class='clearfix'>
|
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
|
||||||
<span class="strong">0% </span>
|
|
||||||
<span class="quiet">Statements</span>
|
|
||||||
<span class='fraction'>0/2</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
|
||||||
<span class="strong">100% </span>
|
|
||||||
<span class="quiet">Branches</span>
|
|
||||||
<span class='fraction'>0/0</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
|
||||||
<span class="strong">100% </span>
|
|
||||||
<span class="quiet">Functions</span>
|
|
||||||
<span class='fraction'>0/0</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
|
||||||
<span class="strong">0% </span>
|
|
||||||
<span class="quiet">Lines</span>
|
|
||||||
<span class='fraction'>0/2</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<p class="quiet">
|
|
||||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
|
||||||
</p>
|
|
||||||
<template id="filterTemplate">
|
|
||||||
<div class="quiet">
|
|
||||||
Filter:
|
|
||||||
<input type="search" id="fileSearch">
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div class='status-line low'></div>
|
|
||||||
<pre><table class="coverage">
|
|
||||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
|
||||||
<a name='L2'></a><a href='#L2'>2</a>
|
|
||||||
<a name='L3'></a><a href='#L3'>3</a>
|
|
||||||
<a name='L4'></a><a href='#L4'>4</a>
|
|
||||||
<a name='L5'></a><a href='#L5'>5</a>
|
|
||||||
<a name='L6'></a><a href='#L6'>6</a>
|
|
||||||
<a name='L7'></a><a href='#L7'>7</a>
|
|
||||||
<a name='L8'></a><a href='#L8'>8</a>
|
|
||||||
<a name='L9'></a><a href='#L9'>9</a>
|
|
||||||
<a name='L10'></a><a href='#L10'>10</a>
|
|
||||||
<a name='L11'></a><a href='#L11'>11</a>
|
|
||||||
<a name='L12'></a><a href='#L12'>12</a>
|
|
||||||
<a name='L13'></a><a href='#L13'>13</a>
|
|
||||||
<a name='L14'></a><a href='#L14'>14</a>
|
|
||||||
<a name='L15'></a><a href='#L15'>15</a>
|
|
||||||
<a name='L16'></a><a href='#L16'>16</a>
|
|
||||||
<a name='L17'></a><a href='#L17'>17</a>
|
|
||||||
<a name='L18'></a><a href='#L18'>18</a>
|
|
||||||
<a name='L19'></a><a href='#L19'>19</a>
|
|
||||||
<a name='L20'></a><a href='#L20'>20</a>
|
|
||||||
<a name='L21'></a><a href='#L21'>21</a>
|
|
||||||
<a name='L22'></a><a href='#L22'>22</a>
|
|
||||||
<a name='L23'></a><a href='#L23'>23</a>
|
|
||||||
<a name='L24'></a><a href='#L24'>24</a>
|
|
||||||
<a name='L25'></a><a href='#L25'>25</a>
|
|
||||||
<a name='L26'></a><a href='#L26'>26</a>
|
|
||||||
<a name='L27'></a><a href='#L27'>27</a>
|
|
||||||
<a name='L28'></a><a href='#L28'>28</a>
|
|
||||||
<a name='L29'></a><a href='#L29'>29</a>
|
|
||||||
<a name='L30'></a><a href='#L30'>30</a>
|
|
||||||
<a name='L31'></a><a href='#L31'>31</a>
|
|
||||||
<a name='L32'></a><a href='#L32'>32</a>
|
|
||||||
<a name='L33'></a><a href='#L33'>33</a>
|
|
||||||
<a name='L34'></a><a href='#L34'>34</a>
|
|
||||||
<a name='L35'></a><a href='#L35'>35</a>
|
|
||||||
<a name='L36'></a><a href='#L36'>36</a>
|
|
||||||
<a name='L37'></a><a href='#L37'>37</a>
|
|
||||||
<a name='L38'></a><a href='#L38'>38</a>
|
|
||||||
<a name='L39'></a><a href='#L39'>39</a>
|
|
||||||
<a name='L40'></a><a href='#L40'>40</a>
|
|
||||||
<a name='L41'></a><a href='#L41'>41</a>
|
|
||||||
<a name='L42'></a><a href='#L42'>42</a>
|
|
||||||
<a name='L43'></a><a href='#L43'>43</a>
|
|
||||||
<a name='L44'></a><a href='#L44'>44</a>
|
|
||||||
<a name='L45'></a><a href='#L45'>45</a>
|
|
||||||
<a name='L46'></a><a href='#L46'>46</a>
|
|
||||||
<a name='L47'></a><a href='#L47'>47</a>
|
|
||||||
<a name='L48'></a><a href='#L48'>48</a>
|
|
||||||
<a name='L49'></a><a href='#L49'>49</a>
|
|
||||||
<a name='L50'></a><a href='#L50'>50</a>
|
|
||||||
<a name='L51'></a><a href='#L51'>51</a>
|
|
||||||
<a name='L52'></a><a href='#L52'>52</a>
|
|
||||||
<a name='L53'></a><a href='#L53'>53</a>
|
|
||||||
<a name='L54'></a><a href='#L54'>54</a>
|
|
||||||
<a name='L55'></a><a href='#L55'>55</a>
|
|
||||||
<a name='L56'></a><a href='#L56'>56</a>
|
|
||||||
<a name='L57'></a><a href='#L57'>57</a>
|
|
||||||
<a name='L58'></a><a href='#L58'>58</a>
|
|
||||||
<a name='L59'></a><a href='#L59'>59</a>
|
|
||||||
<a name='L60'></a><a href='#L60'>60</a>
|
|
||||||
<a name='L61'></a><a href='#L61'>61</a>
|
|
||||||
<a name='L62'></a><a href='#L62'>62</a>
|
|
||||||
<a name='L63'></a><a href='#L63'>63</a>
|
|
||||||
<a name='L64'></a><a href='#L64'>64</a>
|
|
||||||
<a name='L65'></a><a href='#L65'>65</a>
|
|
||||||
<a name='L66'></a><a href='#L66'>66</a>
|
|
||||||
<a name='L67'></a><a href='#L67'>67</a>
|
|
||||||
<a name='L68'></a><a href='#L68'>68</a>
|
|
||||||
<a name='L69'></a><a href='#L69'>69</a>
|
|
||||||
<a name='L70'></a><a href='#L70'>70</a>
|
|
||||||
<a name='L71'></a><a href='#L71'>71</a>
|
|
||||||
<a name='L72'></a><a href='#L72'>72</a>
|
|
||||||
<a name='L73'></a><a href='#L73'>73</a>
|
|
||||||
<a name='L74'></a><a href='#L74'>74</a>
|
|
||||||
<a name='L75'></a><a href='#L75'>75</a>
|
|
||||||
<a name='L76'></a><a href='#L76'>76</a>
|
|
||||||
<a name='L77'></a><a href='#L77'>77</a>
|
|
||||||
<a name='L78'></a><a href='#L78'>78</a>
|
|
||||||
<a name='L79'></a><a href='#L79'>79</a>
|
|
||||||
<a name='L80'></a><a href='#L80'>80</a>
|
|
||||||
<a name='L81'></a><a href='#L81'>81</a>
|
|
||||||
<a name='L82'></a><a href='#L82'>82</a>
|
|
||||||
<a name='L83'></a><a href='#L83'>83</a>
|
|
||||||
<a name='L84'></a><a href='#L84'>84</a>
|
|
||||||
<a name='L85'></a><a href='#L85'>85</a>
|
|
||||||
<a name='L86'></a><a href='#L86'>86</a>
|
|
||||||
<a name='L87'></a><a href='#L87'>87</a>
|
|
||||||
<a name='L88'></a><a href='#L88'>88</a>
|
|
||||||
<a name='L89'></a><a href='#L89'>89</a>
|
|
||||||
<a name='L90'></a><a href='#L90'>90</a>
|
|
||||||
<a name='L91'></a><a href='#L91'>91</a>
|
|
||||||
<a name='L92'></a><a href='#L92'>92</a>
|
|
||||||
<a name='L93'></a><a href='#L93'>93</a>
|
|
||||||
<a name='L94'></a><a href='#L94'>94</a>
|
|
||||||
<a name='L95'></a><a href='#L95'>95</a>
|
|
||||||
<a name='L96'></a><a href='#L96'>96</a>
|
|
||||||
<a name='L97'></a><a href='#L97'>97</a>
|
|
||||||
<a name='L98'></a><a href='#L98'>98</a>
|
|
||||||
<a name='L99'></a><a href='#L99'>99</a>
|
|
||||||
<a name='L100'></a><a href='#L100'>100</a>
|
|
||||||
<a name='L101'></a><a href='#L101'>101</a>
|
|
||||||
<a name='L102'></a><a href='#L102'>102</a>
|
|
||||||
<a name='L103'></a><a href='#L103'>103</a>
|
|
||||||
<a name='L104'></a><a href='#L104'>104</a>
|
|
||||||
<a name='L105'></a><a href='#L105'>105</a>
|
|
||||||
<a name='L106'></a><a href='#L106'>106</a>
|
|
||||||
<a name='L107'></a><a href='#L107'>107</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">// 이 파일이 하는 일: 개발 용어 사전의 '데이터'. 강좌 7개 카테고리에서 나오는 용어 98개를
|
|
||||||
// 초보 눈높이로 정의했다. 화면(GlossaryPage)은 이 배열을 검색·표시만 한다.
|
|
||||||
// 학습 포인트: 데이터와 화면을 나누면, 용어를 추가할 때 여기 한 줄만 넣으면 된다.
|
|
||||||
|
|
||||||
export const GLOSSARY_CATEGORIES = <span class="cstat-no" title="statement not covered" >["컴퓨터 기초", "네트워크", "서버와 데이터", "소프트웨어", "디자인", "앱 개발", "도구·보안"];</span>
|
|
||||||
|
|
||||||
export const GLOSSARY = <span class="cstat-no" title="statement not covered" >[</span>
|
|
||||||
{ term: `CPU (중앙처리장치)`, category: `컴퓨터 기초`, short: `컴퓨터의 두뇌`, desc: `메모리에서 명령어를 가져와(Fetch) 해석하고(Decode) 실행하는(Execute) 명령어 사이클을 1초에 수십억 번 반복하는 핵심 부품이에요. 코어·스레드·클럭 같은 스펙이 모두 이 CPU의 성능을 나타냅니다. 우리 플랫폼을 빌드(npm run build)하거나 서버가 요청을 처리할 때 실제로 일하는 게 CPU예요.` },
|
|
||||||
{ term: `GPU (그래픽처리장치)`, category: `컴퓨터 기초`, short: `화면·대량계산 전담 일꾼`, desc: `화면에 픽셀을 그리려고 단순한 계산을 동시에 수천 개씩 처리하도록 만든 칩이에요. 이 '한꺼번에 많이' 특성 덕분에 게임 그래픽뿐 아니라 AI 학습·추론에도 필수라, AI 시대에 값이 크게 뛰었습니다. CPU가 소수의 복잡한 일을 잘한다면, GPU는 똑같은 계산을 대량으로 밀어붙이는 데 강해요.` },
|
|
||||||
{ term: `RAM (메모리)`, category: `컴퓨터 기초`, short: `지금 작업 중인 책상`, desc: `CPU가 당장 쓰는 데이터를 잠시 올려 두는 빠른 작업 공간으로, 전원이 꺼지면 내용이 전부 사라지는 '휘발성'이에요. 용량이 크다고 속도가 빨라지는 게 아니라 동시에 펼쳐 둘 수 있는 작업이 많아집니다. IDE·브라우저·Docker를 함께 띄우는 개발 환경에선 16GB 이상이 현실적인 최소선이에요.` },
|
|
||||||
{ term: `스토리지 (Storage · SSD·HDD)`, category: `컴퓨터 기초`, short: `전원 꺼져도 남는 책장`, desc: `파일·프로그램을 영구 보관하는 저장장치로, 전원이 꺼져도 내용이 남는 '비휘발성'이라 RAM과 정반대예요. 요즘은 빠른 SSD(특히 NVMe)가 표준이고, 느린 HDD는 대용량 보관용으로 남았습니다. 프로그램을 실행하면 스토리지(책장)에서 꺼내 RAM(책상)에 펼치는 흐름을 알면 컴퓨터가 느려지는 이유도 보여요.` },
|
|
||||||
{ term: `캐시 (Cache)`, category: `컴퓨터 기초`, short: `CPU 손 닿는 곳의 재료 선반`, desc: `자주 쓰는 데이터를 CPU 바로 옆에 미리 올려 둬서 느린 RAM까지 매번 다녀오지 않게 하는 초고속 임시 저장 공간이에요. CPU 안에 L1·L2·L3 세 층으로 있고, 가까울수록 작고 빠릅니다. 원하는 데이터가 캐시에 있으면(캐시 히트) 빠르고, 없어서 RAM까지 가면(캐시 미스) 수십 배 느려져요.` },
|
|
||||||
{ term: `메인보드 (Mainboard · 마더보드)`, category: `컴퓨터 기초`, short: `부품을 잇는 도로망`, desc: `CPU·RAM·그래픽카드·저장장치·주변기기가 전부 꽂혀 서로 데이터를 주고받는 커다란 기판이에요. 부품들의 '도로망'이자 만남의 장소라, 어떤 CPU·RAM을 꽂을 수 있는지가 이 메인보드 규격으로 정해집니다. 데스크탑을 조립할 때 부품 호환성의 기준이 되는 판이에요.` },
|
|
||||||
{ term: `2진수 (Binary · 이진수)`, category: `컴퓨터 기초`, short: `0과 1 두 개로 세는 셈법`, desc: `전기 스위치의 켜짐(1)·꺼짐(0) 두 상태만으로 모든 수를 표현하는 방식으로, 상태가 둘뿐이라 전압이 흔들려도 헷갈리지 않아 컴퓨터의 기본 언어가 됐어요. 숫자뿐 아니라 문자·색·이미지·소리까지 결국 0과 1의 나열로 저장됩니다. 각 자리값(1,2,4,8…)을 더하면 10진수로 바꿀 수 있어요.` },
|
|
||||||
{ term: `비트·바이트 (Bit·Byte)`, category: `컴퓨터 기초`, short: `데이터 크기의 기본 단위`, desc: `비트(bit)는 0 또는 1 한 개, 바이트(Byte)는 비트 8개 묶음으로 영어 한 글자 크기예요. 8비트면 2의 8제곱인 256가지를 표현할 수 있고, KB·MB·GB·TB는 여기에 1,024배씩 곱해 커집니다. 파일 크기·RAM 용량·네트워크 속도가 전부 이 단위로 표시돼요.` },
|
|
||||||
{ term: `운영체제 (OS · Operating System)`, category: `컴퓨터 기초`, short: `하드웨어를 총괄하는 매니저`, desc: `CPU·메모리·디스크 같은 자원을 앱들에 나눠 주고 프로세스·파일·보안 권한을 관리하는, 컴퓨터의 총괄 소프트웨어예요. Windows·macOS는 사람이 쓰기 편하게, Linux는 서버에서 24시간 안정적으로 도는 데 강합니다. 우리 개발 PC는 대개 Windows, AWS EC2 서버는 전부 Linux를 써요.` },
|
|
||||||
{ term: `프로세스 (Process)`, category: `컴퓨터 기초`, short: `지금 실행 중인 프로그램`, desc: `디스크에 저장된 프로그램(레시피)을 실제로 실행해 CPU와 메모리를 쓰며 살아 움직이는 상태를 프로세스(요리 중인 냄비)라고 해요. 크롬 창 하나에도 프로세스가 여러 개 뜨고, OS는 이들을 아주 잘게 번갈아 실행(문맥 교환)해 동시에 도는 것처럼 보이게 합니다. 작업 관리자의 '프로세스' 탭에서 직접 볼 수 있어요.` },
|
|
||||||
{ term: `컴파일 (Compile)`, category: `컴퓨터 기초`, short: `코드를 기계가 읽게 번역`, desc: `사람이 읽고 쓰는 소스 코드를, 컴퓨터가 실행할 수 있는 저수준 형태(기계어나 바이트코드 등)로 번역하는 과정이에요. CPU는 우리가 쓴 Java·JavaScript를 그대로 못 읽어서 실행 전에 이 번역이 필요합니다. npm run build나 ./mvnw package처럼 코드를 실행 파일로 '빌드'할 때 컴파일이 핵심 단계라, 파일이 많을수록 CPU를 크게 써요.` },
|
|
||||||
{ term: `파일시스템 (File System)`, category: `컴퓨터 기초`, short: `디스크에 붙인 정리 장부`, desc: `그냥 0과 1의 바다인 디스크 위에, 어떤 데이터가 어디서 어디까지인지 기록한 '장부'를 얹어 우리가 파일과 폴더로 다루게 해 주는 규칙이에요. Windows는 NTFS, USB·SD카드는 exFAT, 리눅스 서버는 ext4를 주로 씁니다. 파일 이름·크기·위치 같은 메타데이터를 이 파일시스템이 관리해요.` },
|
|
||||||
{ term: `확장자 (File Extension)`, category: `컴퓨터 기초`, short: `파일 뒤에 붙는 이름표`, desc: `파일 이름 끝의 .jpg·.docx 같은 꼬리표로, 어떤 프로그램으로 열지 OS에 알려 주는 '이름표'일 뿐 내용물 자체는 아니에요. 진짜 파일 종류는 맨 앞 몇 바이트(매직 넘버)로 정해지므로, 확장자만 바꾼다고 내용이 바뀌지는 않습니다. 그래서 photo.jpg를 photo.txt로 바꿔도 데이터는 그대로예요.` },
|
|
||||||
{ term: `경로 (Path)`, category: `컴퓨터 기초`, short: `파일까지 찾아가는 주소`, desc: `파일이나 폴더의 위치를 나타내는 주소로, 뿌리(루트)부터 전체를 적는 절대경로와 지금 위치 기준으로 적는 상대경로가 있어요. 리눅스는 /로, Windows는 C:\\로 시작하는 차이가 있습니다. React에서 매일 쓰는 import App from '../App'의 '../'가 바로 '부모 폴더'를 가리키는 상대경로예요.` },
|
|
||||||
{ term: `IP 주소 (IP address)`, category: `네트워크`, short: `인터넷상의 '집 주소'`, desc: `네트워크에 연결된 모든 기기에 붙는 고유 번호로, 데이터가 어디로 가야 할지 정하는 기준이다. 192.168.x.x 같은 사설 IP는 집·회사 내부용이고, 공인 IP는 인터넷 바깥에서 찾을 수 있는 주소다. 우리 학습 플랫폼도 AWS EC2 서버의 IP 위에서 돌아간다.` },
|
|
||||||
{ term: `DNS`, category: `네트워크`, short: `도메인↔IP 전화번호부`, desc: `edu.awesomedevapp.com 같은 외우기 쉬운 이름을 컴퓨터가 쓰는 숫자 IP 주소로 바꿔 주는 시스템이다. 브라우저는 주소를 입력받으면 먼저 DNS에 '이 이름의 IP가 뭐야?'라고 물어본 뒤에야 접속한다. DNS가 없으면 우리는 사이트마다 숫자 IP를 전부 외워야 한다.` },
|
|
||||||
{ term: `HTTP`, category: `네트워크`, short: `브라우저↔서버 대화 규칙`, desc: `웹에서 브라우저(클라이언트)와 서버가 주고받는 요청·응답의 약속된 양식이다. GET(조회)·POST(생성) 같은 메서드로 '무엇을 하고 싶은지'를 밝히고, 서버는 그 결과를 응답으로 돌려준다. 우리 플랫폼의 /api/courses 호출도 전부 이 HTTP 요청이다.` },
|
|
||||||
{ term: `HTTPS / TLS`, category: `네트워크`, short: `암호로 봉인한 HTTP`, desc: `HTTP 통신을 TLS라는 암호 기술로 감싸, 중간에서 누구도 내용을 엿볼 수 없게 만든 것이다. 주소창의 자물쇠(🔒)가 켜져 있으면 아이디·비밀번호가 봉투 속에서 오간다는 뜻이라, 공용 와이파이에서 특히 중요하다. 우리 플랫폼은 Caddy가 인증서를 받아 자동으로 HTTPS를 걸어 준다.` },
|
|
||||||
{ term: `TCP`, category: `네트워크`, short: `확실한 택배`, desc: `데이터를 빠짐없이, 순서대로 전달하는 것을 보장하는 통신 방식이다. 연결을 먼저 맺고(3-way 핸드셰이크) 잃어버린 조각은 다시 보내기 때문에, 파일·로그인·메시지처럼 한 바이트도 틀리면 안 되는 데이터에 쓴다. 우리 플랫폼의 웹·DB 통신은 전부 TCP 위에서 오간다.` },
|
|
||||||
{ term: `UDP`, category: `네트워크`, short: `빠른 확성기 방송`, desc: `확인·재전송 같은 절차를 생략하고 데이터를 그냥 빠르게 내보내는 통신 방식이다. 몇 개 유실돼도 '최신 정보'가 더 중요한 실시간 영상·게임·음성통화에 어울린다. 정확함보다 속도를 고른 선택이라, 안전하지만 느릴 수 있는 TCP와 정반대 성격이다.` },
|
|
||||||
{ term: `포트 (Port)`, category: `네트워크`, short: `IP라는 건물의 방 번호`, desc: `한 기기(IP) 안에서 어떤 프로그램이 통신을 받을지 구분하는 번호다. 443은 HTTPS, 22는 SSH, 5432는 PostgreSQL처럼 유명 서비스는 번호가 전 세계 공통으로 정해져 있다. 우리 백엔드(Spring Boot)가 8080 포트에서 뜨고 localhost:8080으로 접속하는 것도 이 규칙이다.` },
|
|
||||||
{ term: `패킷 (Packet)`, category: `네트워크`, short: `데이터를 나눈 택배 상자`, desc: `큰 데이터는 통째로 못 보내니 작은 조각으로 쪼갠 것이 패킷이며, 상자마다 순서 번호가 붙는다. 상자들이 서로 다른 길로 가도 도착지에서 번호대로 다시 조립하면 원본이 된다. 지금 보는 이 페이지의 글자들도 수십~수백 개 패킷으로 나뉘어 도착한 것이다.` },
|
|
||||||
{ term: `대역폭 (Bandwidth)`, category: `네트워크`, short: `도로의 '차선 수'`, desc: `단위 시간에 통과할 수 있는 데이터의 최대량으로, 흔히 Mbps(초당 메가비트)로 표시한다. 8비트가 1바이트라, 100Mbps라도 실제 다운로드는 약 12.5MB/s에 그친다 — 광고 속도가 체감보다 빨라 보이는 이유다. 지연시간(반응 속도)과는 다른 개념이라는 점이 핵심이다.` },
|
|
||||||
{ term: `상태코드 (HTTP status code)`, category: `네트워크`, short: `서버 대답의 첫마디`, desc: `서버가 요청 결과를 세 자리 숫자로 알려 주는 것으로, 첫 숫자만 봐도 분위기를 안다 — 2xx 성공, 4xx 요청 잘못, 5xx 서버 잘못. 200(성공)·401(로그인 필요)·404(없는 주소)·500(서버 오류)이 대표적이다. API가 안 될 때 '상태코드가 뭐야?'가 디버깅의 첫 질문이다.` },
|
|
||||||
{ term: `쿠키 (Cookie)`, category: `네트워크`, short: `브라우저가 든 번호표`, desc: `서버가 발급해 브라우저에 저장해 두는 작은 데이터로, 이후 요청마다 자동으로 따라붙는다. HTTP는 매 요청이 끝나면 상대를 잊어버리는데(무상태), 쿠키라는 번호표 덕분에 로그인 상태가 계속 유지된다. 우리 플랫폼도 세션 쿠키로 페이지를 옮겨도 '김미림 님'을 알아본다.` },
|
|
||||||
{ term: `방화벽 (Firewall)`, category: `네트워크`, short: `포트를 여닫는 문지기`, desc: `어떤 통신을 허용하고 막을지 규칙으로 걸러 주는 보안 장치다. 꼭 필요한 포트만 열고 나머지는 닫는 '최소 권한'이 기본 원칙이며, AWS에서는 보안 그룹(Security Group)이 이 역할을 한다. 실수로 DB 포트를 인터넷 전체에 열어 두면 큰 사고로 이어질 수 있다.` },
|
|
||||||
{ term: `VPN`, category: `네트워크`, short: `인터넷 위 암호화 터널`, desc: `내 기기와 목적지 사이에 암호화된 통로를 만들어, 중간에서 통신 내용을 못 보게 하고 마치 다른 위치에서 접속하는 것처럼 보이게 한다. 회사 내부망에 안전하게 접속하거나 공용 와이파이에서 자신을 보호할 때 쓴다. 특정 요청만 대신 전하는 프록시보다 더 넓은 범위의 트래픽을 통째로 감싼다.` },
|
|
||||||
{ term: `프록시 (Proxy)`, category: `네트워크`, short: `요청을 대신 전하는 대리인`, desc: `클라이언트와 서버 사이에서 요청·응답을 중계하는 중간 서버다. 요청을 대신 받아 뒤쪽 서버로 넘기는 '리버스 프록시'는 부하 분산·HTTPS 처리·보안에 쓰이며, 우리 플랫폼의 Caddy와 Gateway가 바로 그 예다. VPN이 '터널'이라면 프록시는 '창구 대리인'에 가깝다.` },
|
|
||||||
{ term: `서버 (Server)`, category: `서버와 데이터`, short: `요청 오면 응답 내주는 프로그램`, desc: `서버는 특별한 기계가 아니라 '요청을 기다렸다가 응답을 돌려주는 프로그램'이자 역할이다. 이 플랫폼도 서울 EC2 위의 Caddy·Spring Boot 같은 서버 프로그램이 여러분의 요청을 받아 화면과 데이터를 내려준다. 여러분의 노트북도 서버 프로그램을 켜는 순간 서버가 된다.` },
|
|
||||||
{ term: `클라이언트 (Client)`, category: `서버와 데이터`, short: `서버에 요청 보내는 쪽(손님)`, desc: `클라이언트는 서버에게 "이거 주세요"라고 요청을 보내는 쪽으로, 보통 여러분의 웹 브라우저나 앱이다. 서버가 식당 직원이라면 클라이언트는 주문하는 손님이다. 지금 이 학습 페이지를 열고 있는 브라우저가 바로 클라이언트다.` },
|
|
||||||
{ term: `데이터베이스 (Database, DB)`, category: `서버와 데이터`, short: `데이터를 정리해 보관하는 창고`, desc: `데이터베이스는 회원·강좌·진도 같은 데이터를 표(테이블) 형태로 안전하게 저장하고 빠르게 꺼내 쓰도록 만든 소프트웨어다. 이 플랫폼은 PostgreSQL이라는 관계형 데이터베이스를 쓴다. 앱을 껐다 켜도 데이터가 사라지지 않는 이유가 바로 DB에 담겨 있기 때문이다.` },
|
|
||||||
{ term: `SQL`, category: `서버와 데이터`, short: `DB에게 말 거는 질의 언어`, desc: `SQL(Structured Query Language)은 데이터베이스에 "이 데이터 꺼내줘/넣어줘"라고 지시하는 표준 언어다. SELECT로 조회하고 INSERT·UPDATE·DELETE로 넣고 고치고 지운다. 이 플랫폼의 진짜 회원·진도 테이블을 SQL로 조회하며 배우게 된다.` },
|
|
||||||
{ term: `테이블 · 행 · 열 (Table / Row / Column)`, category: `서버와 데이터`, short: `데이터를 담는 엑셀 같은 표`, desc: `관계형 DB는 데이터를 표(테이블)에 저장하며, 가로 한 줄이 행(레코드=데이터 한 건), 세로 칸이 열(컬럼=속성)이다. 예를 들어 '회원' 테이블에서 한 행은 한 사람이고, '이름'·'이메일' 열은 그 사람의 속성이다. 엑셀 시트를 떠올리면 거의 똑같다.` },
|
|
||||||
{ term: `기본키 · 외래키 (Primary Key / Foreign Key)`, category: `서버와 데이터`, short: `행을 구분하는 번호표와 연결고리`, desc: `기본키(PK)는 테이블에서 각 행을 유일하게 구분하는 값(예: 회원 id)으로, 절대 중복되지 않는 번호표다. 외래키(FK)는 다른 테이블의 기본키를 가리켜 두 표를 잇는 값으로, '이 진도 기록은 몇 번 회원의 것'인지를 연결한다. 이 고리 덕분에 흩어진 표들이 하나의 데이터로 이어진다.` },
|
|
||||||
{ term: `인덱스 (Index)`, category: `서버와 데이터`, short: `DB의 책 뒤 '찾아보기'`, desc: `인덱스는 특정 열의 값을 미리 정렬해 둔 색인으로, 원하는 행을 처음부터 다 뒤지지 않고 빠르게 찾게 해 준다. 책 맨 뒤의 '찾아보기(색인)'와 같은 역할이라 데이터가 많을수록 조회 속도 차이가 크다. 다만 데이터를 넣고 고칠 때는 조금 느려지므로 꼭 필요한 열에만 건다.` },
|
|
||||||
{ term: `캐시 (Cache)`, category: `서버와 데이터`, short: `자주 쓰는 걸 가까이 두기`, desc: `캐시는 자주·반복해서 쓰는 데이터를 원본(DB)보다 빠른 곳에 잠깐 복사해 두어 속도를 높이는 기법이다. 매번 창고(DB)까지 가는 대신 책상 위에서 바로 꺼내 쓰는 셈이다. 이 플랫폼은 Redis 계열인 Valkey를 캐시로 써서 시세·토큰 같은 데이터를 빠르게 내려준다.` },
|
|
||||||
{ term: `NoSQL`, category: `서버와 데이터`, short: `표가 아닌 유연한 저장 방식`, desc: `NoSQL은 관계형 표(SQL) 대신 키-값·문서 같은 유연한 구조로 데이터를 다루는 데이터베이스들을 통칭한다. 정해진 표 구조가 덜 엄격해 아주 빠르거나 대용량 처리에 유리한 대신, 복잡한 관계 조회는 SQL이 더 낫다. 캐시로 쓰는 Redis/Valkey가 대표적인 키-값 NoSQL이다.` },
|
|
||||||
{ term: `API`, category: `서버와 데이터`, short: `프로그램끼리의 약속된 창구`, desc: `API(응용 프로그램 인터페이스)는 프로그램끼리 정해진 규칙으로 요청·응답을 주고받는 창구다. 식당 메뉴판처럼 "이렇게 요청하면 이런 걸 준다"는 약속이라, 화면(프론트엔드)은 서버(백엔드)의 API를 불러 데이터를 받는다. 이 플랫폼의 화면들도 전부 /api/... 주소로 서버에 요청해 채워진다.` },
|
|
||||||
{ term: `REST API`, category: `서버와 데이터`, short: `주소로 자원을 다루는 API 방식`, desc: `REST는 웹 API를 설계하는 대표 방식으로, 데이터를 '자원'으로 보고 URL 주소로 지정한 뒤 GET(조회)·POST(생성)·PUT(수정)·DELETE(삭제) 같은 HTTP 메서드로 다룬다. 규칙이 단순하고 예측 가능해 가장 널리 쓰인다. 이 플랫폼의 /api/auth, /api/market 같은 주소가 REST 스타일이다.` },
|
|
||||||
{ term: `JSON`, category: `서버와 데이터`, short: `데이터를 주고받는 공용 문서 형식`, desc: `JSON은 데이터를 '이름:값' 쌍과 목록으로 적는 가벼운 텍스트 형식으로, API가 데이터를 주고받는 표준 포장지다. {"name":"미림","progress":40}처럼 사람도 읽을 수 있고 거의 모든 프로그래밍 언어가 이해한다. 서버가 보내는 응답, 앱이 보내는 요청 대부분이 JSON이다.` },
|
|
||||||
{ term: `컨테이너 (Container / Docker)`, category: `서버와 데이터`, short: `앱을 통째로 포장한 상자`, desc: `컨테이너는 프로그램과 실행에 필요한 환경을 하나의 상자로 묶어, 어느 컴퓨터에서도 똑같이 돌아가게 하는 기술이다. Docker(도커)가 대표 도구이며 "내 컴퓨터에선 됐는데" 문제를 크게 줄여 준다. 이 플랫폼도 Caddy·Spring Boot·PostgreSQL을 각각 컨테이너로 포장해 docker-compose로 함께 띄운다.` },
|
|
||||||
{ term: `SSH`, category: `서버와 데이터`, short: `원격 서버에 안전하게 접속`, desc: `SSH(Secure Shell)는 멀리 있는 서버에 암호화된 통로로 접속해 터미널 명령을 내리는 방법이다. 비밀번호나 키(key)로 인증하며 오가는 내용이 암호화돼 안전하다. 이 플랫폼 서버(서울 EC2)에 들어가 로그를 보거나 배포할 때 바로 SSH로 접속한다.` },
|
|
||||||
{ term: `변수 (variable)`, category: `소프트웨어`, short: `값을 담아 이름 붙인 상자`, desc: `데이터를 저장해 두고 이름으로 꺼내 쓰는 공간이다. 예를 들어 const title = '미림'처럼 값을 담아두면 코드 여러 곳에서 그 이름으로 재사용할 수 있다. 이 플랫폼 코드 곳곳의 const·let 선언이 모두 변수이며, 코딩 기초에서 가장 먼저 배우는 개념이다.` },
|
|
||||||
{ term: `함수 (function)`, category: `소프트웨어`, short: `입력을 받아 결과를 내는 기계`, desc: `반복되는 일을 하나의 이름으로 묶어 필요할 때 불러 쓰는(호출하는) 코드 덩어리다. 입력값(매개변수)을 받아 처리한 뒤 결과를 돌려준다(return). 버튼을 눌렀을 때의 동작, 강좌 카드를 그리는 로직 등 이 앱의 기능 대부분이 함수 단위로 나뉘어 있다.` },
|
|
||||||
{ term: `조건문·반복문 (control flow)`, category: `소프트웨어`, short: `만약 ~이면, ~인 동안 반복`, desc: `조건문(if)은 상황에 따라 실행할 코드를 고르고, 반복문(for·while)은 같은 일을 여러 번 되풀이한다. 예를 들어 여러 강좌를 화면에 하나씩 그릴 때 반복문(React에서는 배열의 map)이 목록을 돌며 카드를 만들어 낸다. 프로그램이 위에서 아래로만 흐르지 않고 판단하고 반복하게 만드는 기본 도구다.` },
|
|
||||||
{ term: `배열 (array)`, category: `소프트웨어`, short: `번호표 붙은 칸들의 줄`, desc: `여러 값을 순서대로 담고, 0번부터 시작하는 번호(인덱스)로 각 칸에 접근하는 자료구조다. 강좌 목록처럼 '여러 개를 순서대로' 다룰 때 쓴다. 순서대로 훑기는 빠르지만 특정 값이 들어 있는지 처음부터 끝까지 찾으면 개수가 많을수록 느려진다.` },
|
|
||||||
{ term: `스택·큐 (stack & queue)`, category: `소프트웨어`, short: `쌓기 vs 줄서기`, desc: `스택은 나중에 넣은 것을 먼저 꺼내는(LIFO) 구조로, 되돌리기(Undo)나 함수 호출 기록 같은 곳에 쓰인다. 큐는 먼저 들어온 것을 먼저 꺼내는(FIFO) 구조로, 순서대로 처리하는 작업 대기열에 쓰인다. 담는 방식이 아니라 '꺼내는 순서'가 다른, 목적에 맞춘 그릇이다.` },
|
|
||||||
{ term: `해시맵 (hash map)`, category: `소프트웨어`, short: `이름표로 바로 찾는 사전`, desc: `'열쇠(key) → 값(value)' 쌍으로 저장해, 키만 알면 개수와 상관없이 거의 즉시 값을 찾는 자료구조다(자바스크립트의 객체·Map). 배열이 처음부터 뒤지는 것과 달리 '학번으로 학생 찾기'처럼 곧바로 접근한다. 조회가 잦은 곳이나 캐시에서 속도의 핵심이 된다.` },
|
|
||||||
{ term: `컴포넌트 (component)`, category: `소프트웨어`, short: `화면을 이루는 조립 블록`, desc: `화면을 독립적인 조각으로 나눈 재사용 단위로, React 화면을 만드는 기본 블록이다. 바깥에서 받아오는 값을 props, 컴포넌트가 스스로 기억하는 값을 state라 하며, state가 바뀌면 화면이 자동으로 다시 그려진다. 이 플랫폼의 강좌 카드와 각 페이지가 전부 컴포넌트로 만들어져 있다.` },
|
|
||||||
{ term: `라이브러리·프레임워크 (library vs framework)`, category: `소프트웨어`, short: `빌려 쓰는 도구 vs 큰 뼈대`, desc: `라이브러리는 내가 필요할 때 불러다 쓰는 기능 모음(예: 날짜 처리 도구)이고, 프레임워크는 정해진 구조 안에 내 코드를 채워 넣는 큰 뼈대다. '내가 라이브러리를 부르고, 프레임워크가 나를 부른다'가 핵심 차이. 이 앱은 React·Spring Boot라는 프레임워크 위에 여러 라이브러리를 얹어 만든다.` },
|
|
||||||
{ term: `커밋 (commit)`, category: `소프트웨어`, short: `코드 변경의 저장 지점`, desc: `바꾼 내용을 하나의 묶음으로 기록하는 Git의 저장 단위로, '무엇을 왜 바꿨는지' 메시지를 함께 남긴다. 이 스냅샷들이 쌓여 있어 언제든 과거 상태를 돌아보거나 되돌릴 수 있다. 작고 의미 있는 단위로 자주 커밋하는 습관이 협업의 기본기다.` },
|
|
||||||
{ term: `브랜치 (branch)`, category: `소프트웨어`, short: `본류를 건드리지 않는 작업 갈래`, desc: `메인 코드에서 갈라져 나온 독립 작업 공간으로, 새 기능이나 실험을 안전하게 만들다가 완성되면 다시 합친다(merge). 여러 사람이 서로를 방해하지 않고 동시에 일할 수 있게 해준다. 이 저장소의 개발도 main에서 갈라진 작업 브랜치 위에서 이뤄진다.` },
|
|
||||||
{ term: `Pull Request (PR)`, category: `소프트웨어`, short: `내 브랜치를 합쳐달라는 요청`, desc: `내가 만든 브랜치를 메인에 합치기 전에 동료에게 검토(코드리뷰)를 받는 절차이자 화면이다. 변경된 내용을 한눈에 보여주고, 토론과 수정을 거쳐 승인되면 병합된다. 우리 Gitea에서 이뤄지며, 좋은 PR 매너(작은 단위·친절한 설명)는 수습에게 특히 중요한 협업 역량이다.` },
|
|
||||||
{ term: `디버깅 (debugging)`, category: `소프트웨어`, short: `버그를 찾아 고치는 추적`, desc: `프로그램이 잘못 동작하는 원인(버그)을 찾아 고치는 과정이다. 에러의 스택트레이스(어디서 터졌는지 알려주는 호출 경로)를 읽고, 브레이크포인트로 실행을 멈춰 값을 확인하며 단서를 좁혀간다. 에러 메시지는 혼내는 말이 아니라 '여기를 보라'는 단서라는 태도가 핵심이다.` },
|
|
||||||
{ term: `리팩터링 (refactoring)`, category: `소프트웨어`, short: `동작은 그대로, 속을 정리`, desc: `겉으로 보이는 동작은 바꾸지 않으면서 코드 내부를 더 읽기 쉽고 고치기 쉽게 다듬는 작업이다. 좋은 이름 짓기, 길고 복잡한 함수 쪼개기 등이 대표적이다. 테스트 코드가 있으면 '정리한 뒤에도 똑같이 동작하는지'를 확인할 수 있어 안심하고 리팩터링할 수 있다.` },
|
|
||||||
{ term: `테스트 코드 (test)`, category: `소프트웨어`, short: `코드를 검사하는 코드`, desc: `내 코드가 기대대로 동작하는지 자동으로 확인하도록 짠 코드다. 한 번 만들어두면 기능을 고칠 때마다 자동으로 재검사해, 예전에 잘 되던 것이 조용히 망가지는 사고를 막아준다. '미래의 나를 지키는 보험'이라 불리며, 이 플랫폼에서도 실제 테스트가 학습 교재로 쓰인다.` },
|
|
||||||
{ term: `UI/UX`, category: `디자인`, short: `화면 겉모습(UI) + 쓰는 경험(UX)`, desc: `UI(User Interface)는 버튼·색·글자처럼 눈에 보이는 화면 요소이고, UX(User Experience)는 그 화면을 쓰면서 겪는 편함·흐름 전체다. 둘 다 좋아야 좋은 제품이며, 예쁘기만 하고 쓰기 불편하면 UX가 실패한 것이다. 이 학습 플랫폼의 강좌 화면도 UI와 UX를 함께 설계한 결과다.` },
|
|
||||||
{ term: `시각적 위계 (Visual Hierarchy)`, category: `디자인`, short: `뭘 먼저 볼지 정해 주는 순서`, desc: `크기·굵기·색·위치 차이로 화면 요소에 '먼저 볼 것 → 나중에 볼 것'의 순서를 만드는 것이다. 사람은 글을 꼼꼼히 읽기보다 훑어보기(스캔) 때문에, 제목을 크고 진하게·보조 정보를 작고 연하게 두면 눈길을 원하는 곳으로 이끈다. 학습 센터의 강좌 카드에서 제목이 설명글보다 크고 진한 이유가 이것이다.` },
|
|
||||||
{ term: `대비 (Contrast)`, category: `디자인`, short: `서로 달라서 눈에 띄는 정도`, desc: `두 요소의 밝기·색·크기 차이를 말하며, 차이가 클수록 잘 구분되고 눈에 띈다. 특히 글자색과 배경색의 명도 대비가 낮으면 흐릿해 읽기 어려워지므로 가독성과 접근성의 핵심 기준이 된다. 강조 버튼을 브랜드 블루로 칠해 회색 배경과 대비를 준 것이 그 예다.` },
|
|
||||||
{ term: `타이포그래피 (Typography)`, category: `디자인`, short: `글자를 읽기 좋게 다루는 기술`, desc: `폰트 종류, 글자 크기, 굵기, 줄 간격(행간)을 조절해 글을 읽기 좋게 배치하는 기술이다. 화면 정보의 대부분이 글자라 디자인 완성도를 크게 좌우하며, 본문은 보통 15~16px에 행간 1.5~1.6, 제목은 더 크게 두어 위계를 만든다. 세리프(명조처럼 삐침 있음)와 산세리프(고딕처럼 깔끔함)를 구분하는 것이 기본이다.` },
|
|
||||||
{ term: `그리드 (Grid)`, category: `디자인`, short: `화면을 세우는 보이지 않는 모눈종이`, desc: `요소들을 일정한 간격과 열에 맞춰 배치하도록 돕는 보이지 않는 격자다. 특히 모든 간격·크기를 8의 배수(8·16·24px)로 맞추는 8pt 그리드를 쓰면 '몇 px 띄울까' 고민이 줄고 화면이 저절로 정돈된다. 여러 카드의 윗변·왼쪽이 같은 선에 맞으면 그리드를 꺼도 깔끔해 보인다.` },
|
|
||||||
{ term: `여백 (Whitespace)`, category: `디자인`, short: `일부러 비워 둔 빈 공간`, desc: `요소 사이나 주변에 일부러 남겨 둔 빈 공간으로, 흰색이 아니어도 여백이라 부른다. 비어 있어 낭비처럼 보이지만 관련된 것을 묶어 주고 화면에 숨 쉴 틈을 줘 오히려 읽기 쉽고 정돈돼 보이게 한다. 안쪽 여백(padding)은 바깥 여백(margin)보다 작거나 같게 두는 것이 기본 감각이다.` },
|
|
||||||
{ term: `벡터 그래픽 (SVG)`, category: `디자인`, short: `확대해도 안 깨지는 '그리는 법' 그림`, desc: `이미지는 크게 두 종류다. 비트맵(PNG·JPG)은 칸마다 색을 기록해 확대하면 계단처럼 깨지고, 벡터(SVG)는 '어떻게 그리는지'를 수식으로 기록해 아무리 키워도 매끈하다. 그래서 아이콘·로고처럼 여러 크기로 쓰는 그림은 SVG가 유리하고, 사진처럼 색이 복잡한 것은 비트맵을 쓴다.` },
|
|
||||||
{ term: `와이어프레임 (Wireframe)`, category: `디자인`, short: `색 없이 구조만 그린 설계 도면`, desc: `색·이미지·폰트 없이 네모와 선만으로 화면의 뼈대(무엇이 어디에, 얼마나 크게 놓이는지)를 그린 밑그림이다. 코드로 만들기 전에 빠르게 그려 보고 아니면 버리는 용도라, 완성도를 일부러 낮춰 고치는 비용을 줄인다. 보통 손그림(Lo-Fi) → 회색 박스(Mid-Fi) → 시안(Hi-Fi) 순으로 완성도를 올린다.` },
|
|
||||||
{ term: `프로토타입 (Prototype)`, category: `디자인`, short: `진짜처럼 눌러 보는 가짜 앱`, desc: `실제로 코딩하기 전에, 화면들을 연결해 버튼을 누르면 다음 화면으로 넘어가도록 만든 '눌러 볼 수 있는 모형'이다. 피그마 같은 도구에서 화면을 선으로 이어 만들며, 개발 없이도 흐름의 어색함이나 빠진 화면을 미리 발견할 수 있다. 와이어프레임이 정지 그림이라면 프로토타입은 움직이는 시연본이다.` },
|
|
||||||
{ term: `피그마 (Figma)`, category: `디자인`, short: `브라우저에서 도는 협업 디자인 툴`, desc: `웹 브라우저에서 바로 쓰는 화면 디자인 도구로, 무료 계정으로 시작할 수 있고 여러 명이 실시간으로 같은 파일을 함께 편집한다. 프레임(화면 틀)·오토레이아웃(자동 정렬)·컴포넌트(재사용 부품)·프로토타입 기능으로 와이어프레임부터 시안·개발 전달(핸드오프)까지 한 곳에서 처리한다. 오늘날 UI 디자인의 사실상 표준 도구다.` },
|
|
||||||
{ term: `디자인 시스템 (Design System)`, category: `디자인`, short: `색·글꼴·부품의 공식 규칙집`, desc: `색·간격·글꼴 같은 값(토큰)과 버튼·카드 같은 부품(컴포넌트)에 이름과 규칙을 정해 팀 전체가 똑같이 쓰도록 묶은 것이다. 사람마다 제각각 디자인하는 것을 막아 일관성을 지키고 작업 속도를 높인다. 이 플랫폼의 global.css에 정의된 색·간격 변수가 바로 살아 있는 디자인 시스템의 예다.` },
|
|
||||||
{ term: `컴포넌트 (Component)`, category: `디자인`, short: `재사용하는 화면 부품`, desc: `버튼·카드·입력창처럼 여러 곳에서 반복해 쓰는 화면 조각을 하나의 부품으로 만들어 둔 것이다. 피그마에서는 원본(마스터) 하나만 고치면 복제본이 전부 함께 바뀌어, 디자인을 수정할 때 일일이 손볼 필요가 없다. 디자인 시스템의 '2층'에 해당하며, 토큰(색·간격)을 조합해 만든다.` },
|
|
||||||
{ term: `터치 타깃 (Touch Target)`, category: `디자인`, short: `손가락으로 누르는 영역의 크기`, desc: `버튼·링크처럼 손가락으로 눌러야 하는 영역과 그 최소 크기를 말한다. 마우스 포인터보다 손가락 끝이 뭉툭해서, 모바일에서는 최소 44×44px 정도를 확보해야 빗나가지 않고 편하게 눌린다. 또 엄지가 편히 닿는 화면 아래쪽에 자주 쓰는 버튼(하단 탭바 등)을 두는 것이 모바일 설계의 기본이다.` },
|
|
||||||
{ term: `접근성 (Accessibility)`, category: `디자인`, short: `누구나 쓸 수 있게 만드는 배려`, desc: `시력이 약하거나 색을 구분하기 어렵거나 마우스를 못 쓰는 사람까지 포함해, 모두가 제품을 쓸 수 있게 설계하는 것이다. 글자·배경의 명도 대비를 충분히 주기, 이미지에 대체 텍스트(alt) 달기, 색만으로 정보를 전하지 않기 등이 대표 실천이다. 특정 소수만을 위한 것 같지만 결국 모든 사용자의 편의를 높인다.` },
|
|
||||||
{ term: `네이티브 앱 (Native App)`, category: `앱 개발`, short: `그 OS 전용 언어로 만든 앱`, desc: `안드로이드는 Kotlin/Java, iOS는 Swift처럼 각 운영체제 전용 도구로 만든 앱이다. 카메라·센서 등 기기 기능을 가장 빠르고 완전하게 쓸 수 있지만, 두 플랫폼을 따로 개발해야 해 품이 많이 든다. 이 카테고리에서 크로스플랫폼·PWA와 비교하며 '언제 네이티브를 고르나'를 배운다.` },
|
|
||||||
{ term: `크로스플랫폼 (Cross-platform)`, category: `앱 개발`, short: `코드 하나로 안드로이드·iOS 둘 다`, desc: `한 벌의 코드로 안드로이드와 iOS 앱을 함께 만드는 방식으로, React Native·Flutter가 대표 주자다. 개발 인력과 시간을 아낄 수 있어 스타트업이 즐겨 쓴다. React를 아는 웹 개발자가 앱으로 넘어갈 때 첫 다리가 된다.` },
|
|
||||||
{ term: `안드로이드 (Android)`, category: `앱 개발`, short: `구글의 모바일 OS(로봇 🤖)`, desc: `구글이 만든 모바일 운영체제로 전 세계 스마트폰 점유율이 가장 높다. 앱은 주로 Kotlin으로 Android Studio에서 개발하고 APK/AAB 파일로 배포한다. 화면 단위인 '액티비티'와 그 생명주기가 앱 동작의 기본 골격이다.` },
|
|
||||||
{ term: `iOS`, category: `앱 개발`, short: `아이폰용 애플 OS(사과 🍎)`, desc: `애플의 아이폰·아이패드 운영체제로, 앱은 Swift 언어와 Xcode 도구로 개발하며 개발에 맥(Mac)이 필요하다. 생태계 규칙과 심사가 엄격한 대신 사용자 결제·품질 기대가 높다. 안드로이드와 함께 모바일 앱의 양대 축이다.` },
|
|
||||||
{ term: `Kotlin (코틀린)`, category: `앱 개발`, short: `안드로이드 공식 1순위 언어`, desc: `JetBrains가 만든 언어로, 구글이 안드로이드 개발 1순위로 공식 지정했다. Java와 호환되면서 문법이 간결하고 null 안전성이 좋아 흔한 실수를 줄여준다. 안드로이드 네이티브 앱을 만들 때 가장 먼저 배우는 언어다.` },
|
|
||||||
{ term: `Swift (스위프트)`, category: `앱 개발`, short: `iOS 앱 만드는 애플 언어`, desc: `애플이 iOS·macOS 앱을 위해 만든 언어로, 안전하고 읽기 쉬운 문법이 특징이다. 최신 UI 도구인 SwiftUI와 짝을 이뤄 화면을 선언형으로 그린다. 아이폰 네이티브 앱 개발에 꼭 필요한 언어다.` },
|
|
||||||
{ term: `React Native (리액트 네이티브)`, category: `앱 개발`, short: `React로 진짜 앱 만들기`, desc: `웹에서 쓰는 React 문법 그대로 안드로이드·iOS 네이티브 앱을 만드는 크로스플랫폼 프레임워크다. 웹 지식이 거의 그대로 이어져 웹 개발자의 앱 입문 다리로 좋다. 이 플랫폼에서 쌓은 React 경험을 앱으로 확장하는 지름길이다.` },
|
|
||||||
{ term: `Flutter (플러터)`, category: `앱 개발`, short: `위젯 레고로 앱 조립(Dart)`, desc: `구글이 만든 크로스플랫폼 프레임워크로, Dart 언어를 쓰고 화면의 모든 것을 '위젯(Widget)' 블록으로 조립한다. 자체 렌더링 엔진으로 안드로이드·iOS에서 똑같은 화면을 빠르게 그린다. React Native와 함께 크로스플랫폼의 양대 선택지다.` },
|
|
||||||
{ term: `앱 생명주기 (App Lifecycle)`, category: `앱 개발`, short: `앱이 켜지고 멈추는 단계들`, desc: `앱 화면이 생성→표시→가려짐→종료로 이어지는 정해진 단계와, 각 단계에서 자동으로 호출되는 함수들을 말한다(안드로이드 액티비티, iOS 뷰컨트롤러). 전화가 와서 앱이 잠시 멈출 때 데이터를 안전하게 저장하려면 이 흐름을 알아야 한다. '중단·복귀'가 잦은 모바일의 핵심 개념이다.` },
|
|
||||||
{ term: `PWA (프로그레시브 웹 앱)`, category: `앱 개발`, short: `설치되는 웹앱`, desc: `브라우저로 만든 웹사이트를 홈 화면 설치·오프라인·푸시 알림까지 되게 해 앱처럼 쓰게 하는 기술이다. 스토어 심사 없이 배포할 수 있어, 우리 웹앱(React) 자산을 가장 적은 비용으로 앱화하는 길이다. 서비스 워커와 매니페스트가 두 기둥이다.` },
|
|
||||||
{ term: `서비스 워커 (Service Worker)`, category: `앱 개발`, short: `웹앱 뒤에서 도는 일꾼`, desc: `웹페이지와 별개로 브라우저 뒤에서 도는 스크립트로, 네트워크 요청을 가로채 캐시로 응답하거나(오프라인) 푸시 알림을 받는다. PWA가 인터넷 없이도 동작하게 하는 핵심 부품이다. 화면이 닫혀 있어도 살아 있어 백그라운드 작업을 맡는다.` },
|
|
||||||
{ term: `웹 앱 매니페스트 (Web App Manifest)`, category: `앱 개발`, short: `웹앱의 신분증(JSON)`, desc: `앱 이름·아이콘·시작 주소·화면 방향 등을 적어 둔 JSON 파일로, 브라우저가 이걸 읽어 웹앱을 홈 화면에 '설치'해 준다. PWA를 진짜 앱처럼 보이게 하는 설정표다. (안드로이드 네이티브의 AndroidManifest.xml과 이름은 비슷하지만 다른 것이다.)` },
|
|
||||||
{ term: `앱 권한 (Permission)`, category: `앱 개발`, short: `카메라·위치 쓰겠다는 허락`, desc: `앱이 카메라·위치·연락처·알림 등 민감한 기능에 접근하려면 사용자에게 받아야 하는 동의다. 필요한 순간에 꼭 필요한 것만 요청하는 게 원칙이며, 과한 권한 요구는 스토어 심사에서 거절 사유가 된다. 앱 보안과 신뢰의 첫 단추다.` },
|
|
||||||
{ term: `스토어 심사 (App Store Review)`, category: `앱 개발`, short: `앱 등록 전 통과 시험`, desc: `만든 앱을 구글 Play·애플 App Store에 올리기 전, 플랫폼이 정책·보안·품질 기준으로 검토하는 과정이다. 특히 애플은 심사가 까다로워 반려되면 수정 후 재제출해야 한다. 배포 일정을 잡을 때 이 심사 기간을 반드시 감안해야 한다.` },
|
|
||||||
{ term: `터미널 / 셸 (Terminal / Shell)`, category: `도구·보안`, short: `글자로 명령하는 검은 창`, desc: `터미널은 명령어를 입력하고 결과가 글자로 나오는 창(화면)이고, 셸(shell)은 그 안에서 내가 친 명령을 해석해 실행하는 프로그램이에요. 하나의 Windows Terminal 창에 PowerShell·Git Bash 같은 여러 셸을 탭으로 열 수 있어요. 우리 서버(AWS EC2)는 리눅스라 접속하면 결국 터미널로 다루게 되니, 개발의 현관문 같은 도구입니다.` },
|
|
||||||
{ term: `CLI (명령줄 인터페이스)`, category: `도구·보안`, short: `마우스 대신 명령어로 조작`, desc: `CLI(Command Line Interface)는 버튼·아이콘을 클릭하는 대신 명령어를 타이핑해서 프로그램을 다루는 방식으로, 마우스로 누르는 GUI의 반대말이에요. git·npm·docker 같은 개발 도구 대부분이 CLI로 동작해요. 손에 익으면 반복 작업을 훨씬 빠르고 정확하게, 그리고 자동화까지 할 수 있습니다.` },
|
|
||||||
{ term: `IDE (통합 개발 환경)`, category: `도구·보안`, short: `코딩 도구를 한 상자에`, desc: `IDE(Integrated Development Environment)는 코드 편집·실행·디버깅·검색을 한곳에 묶어 둔 프로그램이에요. 자바 쪽 IntelliJ가 대표적이고, VS Code도 확장을 더하면 IDE처럼 쓸 수 있죠. 자동완성·오류 표시·단축키 덕분에 메모장으로 코딩할 때보다 훨씬 빠르고 실수도 줄어듭니다.` },
|
|
||||||
{ term: `환경변수 (Environment Variable)`, category: `도구·보안`, short: `코드 밖에 숨겨 두는 설정값`, desc: `환경변수는 프로그램 바깥(운영체제·실행 환경)에 저장해 두고 실행될 때 꺼내 쓰는 값이에요. DB 비밀번호·API 키 같은 비밀값을 코드나 Git에 넣지 않고 환경변수로 주입하는 게 보안의 기본입니다. 우리 플랫폼도 .env 파일로 값만 바꿔 개발용·운영용을 같은 코드로 돌려요.` },
|
|
||||||
{ term: `패키지 매니저 (Package Manager)`, category: `도구·보안`, short: `라이브러리 설치·관리 도구`, desc: `패키지 매니저는 남이 만든 코드 묶음(라이브러리)을 명령 한 줄로 내려받고 버전을 관리해 주는 도구예요. 프론트의 npm, 자바의 Maven이 대표적이죠. npm install 한 번이면 프로젝트에 필요한 수많은 패키지를 알아서 맞춰 설치해 줍니다.` },
|
|
||||||
{ term: `의존성 (Dependency)`, category: `도구·보안`, short: `내가 빌려 쓰는 남의 코드`, desc: `의존성은 내 프로젝트가 돌아가려면 필요한 외부 라이브러리, 즉 빌려 쓰는 코드예요. package.json이나 pom.xml에 목록이 적혀 있고 패키지 매니저가 그 목록대로 설치하죠. 편리하지만 버전이 안 맞거나 보안 취약점이 있는 의존성은 문제를 일으킬 수 있어 관리가 중요합니다.` },
|
|
||||||
{ term: `인증 / 인가 (Authentication / Authorization)`, category: `도구·보안`, short: `누구인가 vs 뭘 해도 되나`, desc: `인증(Authentication)은 "네가 진짜 그 사람 맞아?"를 확인하는 것(로그인), 인가(Authorization)는 "그럼 이건 해도 돼?"라며 권한을 따지는 것이에요. 로그인은 됐지만 관리자 메뉴엔 못 들어가는 게 인증은 통과·인가는 거부된 상태죠. 우리 플랫폼도 로그인(인증) 뒤 학생/멘토 권한(인가)을 나눠 화면과 API 접근을 제어해요.` },
|
|
||||||
{ term: `해싱 (Hashing)`, category: `도구·보안`, short: `되돌릴 수 없는 한 방향 지문`, desc: `해싱은 어떤 값을 규칙에 따라 알아볼 수 없는 고정 길이 문자열로 바꾸되, 원래 값으로 되돌릴 수 없게 만드는 것이에요(복호화되는 암호화와 다른 점). 비밀번호는 평문 저장 대신 반드시 해싱해 보관하는데, 우리 백엔드도 BCrypt로 해싱해서 DB가 털려도 원문 비밀번호는 알 수 없게 합니다.` },
|
|
||||||
{ term: `암호화 (Encryption)`, category: `도구·보안`, short: `열쇠로 잠갔다 푸는 비밀문`, desc: `암호화는 데이터를 열쇠(키)로 잠가 아무나 못 읽게 만들고, 올바른 열쇠로만 다시 풀(복호화) 수 있게 하는 기술이에요. 해싱과 달리 되돌릴 수 있다는 점이 다르죠. 웹의 HTTPS(TLS)와 와이파이의 WPA가 전부 암호화라, 공용 와이파이에서 누가 엿봐도 암호문만 보이게 됩니다.` },
|
|
||||||
{ term: `세션 / 쿠키 (Session / Cookie)`, category: `도구·보안`, short: `로그인 상태를 기억하는 장치`, desc: `HTTP는 매 요청을 남처럼 대해서, 로그인 상태를 유지하려면 별도 장치가 필요해요. 쿠키는 브라우저가 서버에서 받아 저장했다가 요청마다 자동으로 딸려 보내는 작은 데이터 조각이고, 세션은 서버가 "이 사람은 로그인된 아무개"라고 기억하는 상태예요. 로그인 정보가 든 쿠키를 훔치면 남의 계정이 되므로 httpOnly·HTTPS로 보호합니다.` },
|
|
||||||
{ term: `최소 권한 원칙 (Least Privilege)`, category: `도구·보안`, short: `딱 필요한 만큼만 권한을`, desc: `최소 권한 원칙은 사람·프로그램·계정에게 일하는 데 꼭 필요한 최소한의 권한만 주는 보안의 기본 철학이에요. 한 곳이 뚫려도 피해를 그 범위에 가두는 게 목적이죠. 우리 AWS 보안그룹이 필요한 포트만 열고 나머지는 닫는 것, DB 계정에 DROP 권한을 안 주는 것이 모두 이 원칙입니다.` },
|
|
||||||
{ term: `무차별 대입 (Brute Force)`, category: `도구·보안`, short: `비밀번호를 기계로 마구 찍기`, desc: `무차별 대입(브루트포스)은 가능한 아이디·비밀번호 조합을 자동으로 수만 번 시도해 로그인을 뚫으려는 공격이에요. 인터넷에 서버를 올리면 봇이 곧바로 SSH·로그인 화면을 두드리기 시작하죠. 강한 비밀번호, 로그인 시도 횟수 제한, IP 제한, 2단계 인증이 이를 막는 기본 방어입니다.` },
|
|
||||||
{ term: `SQL 인젝션 (SQL Injection)`, category: `도구·보안`, short: `입력창에 DB 명령 몰래 끼우기`, desc: `SQL 인젝션은 로그인·검색 입력창에 SQL 문법을 섞어 넣어, 서버가 그걸 명령으로 잘못 실행하게 만드는 공격이에요. 성공하면 남의 데이터를 통째로 빼내거나 지울 수도 있죠. 사용자 입력을 쿼리에 직접 이어 붙이지 않고 파라미터로 분리(프리페어드 스테이트먼트)하면 막을 수 있습니다.` },
|
|
||||||
{ term: `XSS (크로스 사이트 스크립팅)`, category: `도구·보안`, short: `페이지에 악성 스크립트 심기`, desc: `XSS는 게시글·댓글 같은 입력에 악성 자바스크립트를 심어, 그 페이지를 보는 다른 사용자의 브라우저에서 실행되게 하는 공격이에요. 남의 쿠키나 로그인 정보를 훔치는 데 자주 쓰이죠. 사용자 입력을 화면에 그대로 출력하지 않고 특수문자를 이스케이프 처리하면 막을 수 있습니다.` },
|
|
||||||
];
|
|
||||||
</pre></td></tr></table></pre>
|
|
||||||
|
|
||||||
<div class='push'></div><!-- for sticky footer -->
|
|
||||||
</div><!-- /wrapper -->
|
|
||||||
<div class='footer quiet pad2 space-top1 center small'>
|
|
||||||
Code coverage generated by
|
|
||||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
|
||||||
at 2026-07-17T08:57:03.899Z
|
|
||||||
</div>
|
|
||||||
<script src="prettify.js"></script>
|
|
||||||
<script>
|
|
||||||
window.onload = function () {
|
|
||||||
prettyPrint();
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
<script src="sorter.js"></script>
|
|
||||||
<script src="block-navigation.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
@ -1,161 +0,0 @@
|
|||||||
|
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<title>Code coverage report for All files</title>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<link rel="stylesheet" href="prettify.css" />
|
|
||||||
<link rel="stylesheet" href="base.css" />
|
|
||||||
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<style type='text/css'>
|
|
||||||
.coverage-summary .sorter {
|
|
||||||
background-image: url(sort-arrow-sprite.png);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div class='wrapper'>
|
|
||||||
<div class='pad1'>
|
|
||||||
<h1>All files</h1>
|
|
||||||
<div class='clearfix'>
|
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
|
||||||
<span class="strong">18.84% </span>
|
|
||||||
<span class="quiet">Statements</span>
|
|
||||||
<span class='fraction'>13/69</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
|
||||||
<span class="strong">28.57% </span>
|
|
||||||
<span class="quiet">Branches</span>
|
|
||||||
<span class='fraction'>4/14</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
|
||||||
<span class="strong">21.05% </span>
|
|
||||||
<span class="quiet">Functions</span>
|
|
||||||
<span class='fraction'>4/19</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
|
||||||
<span class="strong">16.39% </span>
|
|
||||||
<span class="quiet">Lines</span>
|
|
||||||
<span class='fraction'>10/61</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<p class="quiet">
|
|
||||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
|
||||||
</p>
|
|
||||||
<template id="filterTemplate">
|
|
||||||
<div class="quiet">
|
|
||||||
Filter:
|
|
||||||
<input type="search" id="fileSearch">
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div class='status-line low'></div>
|
|
||||||
<div class="pad1">
|
|
||||||
<table class="coverage-summary">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
|
|
||||||
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
|
|
||||||
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
|
|
||||||
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
|
|
||||||
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
|
|
||||||
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
|
|
||||||
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
|
|
||||||
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
|
|
||||||
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
|
|
||||||
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody><tr>
|
|
||||||
<td class="file low" data-value="glossary.js"><a href="glossary.js.html">glossary.js</a></td>
|
|
||||||
<td data-value="0" class="pic low">
|
|
||||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
|
||||||
</td>
|
|
||||||
<td data-value="0" class="pct low">0%</td>
|
|
||||||
<td data-value="2" class="abs low">0/2</td>
|
|
||||||
<td data-value="100" class="pct high">100%</td>
|
|
||||||
<td data-value="0" class="abs high">0/0</td>
|
|
||||||
<td data-value="100" class="pct high">100%</td>
|
|
||||||
<td data-value="0" class="abs high">0/0</td>
|
|
||||||
<td data-value="0" class="pct low">0%</td>
|
|
||||||
<td data-value="2" class="abs low">0/2</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td class="file low" data-value="postcode.js"><a href="postcode.js.html">postcode.js</a></td>
|
|
||||||
<td data-value="0" class="pic low">
|
|
||||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
|
||||||
</td>
|
|
||||||
<td data-value="0" class="pct low">0%</td>
|
|
||||||
<td data-value="18" class="abs low">0/18</td>
|
|
||||||
<td data-value="0" class="pct low">0%</td>
|
|
||||||
<td data-value="4" class="abs low">0/4</td>
|
|
||||||
<td data-value="0" class="pct low">0%</td>
|
|
||||||
<td data-value="6" class="abs low">0/6</td>
|
|
||||||
<td data-value="0" class="pct low">0%</td>
|
|
||||||
<td data-value="17" class="abs low">0/17</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td class="file low" data-value="runJs.js"><a href="runJs.js.html">runJs.js</a></td>
|
|
||||||
<td data-value="0" class="pic low">
|
|
||||||
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
|
|
||||||
</td>
|
|
||||||
<td data-value="0" class="pct low">0%</td>
|
|
||||||
<td data-value="21" class="abs low">0/21</td>
|
|
||||||
<td data-value="0" class="pct low">0%</td>
|
|
||||||
<td data-value="2" class="abs low">0/2</td>
|
|
||||||
<td data-value="0" class="pct low">0%</td>
|
|
||||||
<td data-value="6" class="abs low">0/6</td>
|
|
||||||
<td data-value="0" class="pct low">0%</td>
|
|
||||||
<td data-value="18" class="abs low">0/18</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td class="file low" data-value="runSql.js"><a href="runSql.js.html">runSql.js</a></td>
|
|
||||||
<td data-value="46.42" class="pic low">
|
|
||||||
<div class="chart"><div class="cover-fill" style="width: 46%"></div><div class="cover-empty" style="width: 54%"></div></div>
|
|
||||||
</td>
|
|
||||||
<td data-value="46.42" class="pct low">46.42%</td>
|
|
||||||
<td data-value="28" class="abs low">13/28</td>
|
|
||||||
<td data-value="50" class="pct medium">50%</td>
|
|
||||||
<td data-value="8" class="abs medium">4/8</td>
|
|
||||||
<td data-value="57.14" class="pct medium">57.14%</td>
|
|
||||||
<td data-value="7" class="abs medium">4/7</td>
|
|
||||||
<td data-value="41.66" class="pct low">41.66%</td>
|
|
||||||
<td data-value="24" class="abs low">10/24</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div class='push'></div><!-- for sticky footer -->
|
|
||||||
</div><!-- /wrapper -->
|
|
||||||
<div class='footer quiet pad2 space-top1 center small'>
|
|
||||||
Code coverage generated by
|
|
||||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
|
||||||
at 2026-07-17T08:57:03.899Z
|
|
||||||
</div>
|
|
||||||
<script src="prettify.js"></script>
|
|
||||||
<script>
|
|
||||||
window.onload = function () {
|
|
||||||
prettyPrint();
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
<script src="sorter.js"></script>
|
|
||||||
<script src="block-navigation.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
@ -1,268 +0,0 @@
|
|||||||
|
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<title>Code coverage report for postcode.js</title>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<link rel="stylesheet" href="prettify.css" />
|
|
||||||
<link rel="stylesheet" href="base.css" />
|
|
||||||
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<style type='text/css'>
|
|
||||||
.coverage-summary .sorter {
|
|
||||||
background-image: url(sort-arrow-sprite.png);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div class='wrapper'>
|
|
||||||
<div class='pad1'>
|
|
||||||
<h1><a href="index.html">All files</a> postcode.js</h1>
|
|
||||||
<div class='clearfix'>
|
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
|
||||||
<span class="strong">0% </span>
|
|
||||||
<span class="quiet">Statements</span>
|
|
||||||
<span class='fraction'>0/18</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
|
||||||
<span class="strong">0% </span>
|
|
||||||
<span class="quiet">Branches</span>
|
|
||||||
<span class='fraction'>0/4</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
|
||||||
<span class="strong">0% </span>
|
|
||||||
<span class="quiet">Functions</span>
|
|
||||||
<span class='fraction'>0/6</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
|
||||||
<span class="strong">0% </span>
|
|
||||||
<span class="quiet">Lines</span>
|
|
||||||
<span class='fraction'>0/17</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<p class="quiet">
|
|
||||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
|
||||||
</p>
|
|
||||||
<template id="filterTemplate">
|
|
||||||
<div class="quiet">
|
|
||||||
Filter:
|
|
||||||
<input type="search" id="fileSearch">
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div class='status-line low'></div>
|
|
||||||
<pre><table class="coverage">
|
|
||||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
|
||||||
<a name='L2'></a><a href='#L2'>2</a>
|
|
||||||
<a name='L3'></a><a href='#L3'>3</a>
|
|
||||||
<a name='L4'></a><a href='#L4'>4</a>
|
|
||||||
<a name='L5'></a><a href='#L5'>5</a>
|
|
||||||
<a name='L6'></a><a href='#L6'>6</a>
|
|
||||||
<a name='L7'></a><a href='#L7'>7</a>
|
|
||||||
<a name='L8'></a><a href='#L8'>8</a>
|
|
||||||
<a name='L9'></a><a href='#L9'>9</a>
|
|
||||||
<a name='L10'></a><a href='#L10'>10</a>
|
|
||||||
<a name='L11'></a><a href='#L11'>11</a>
|
|
||||||
<a name='L12'></a><a href='#L12'>12</a>
|
|
||||||
<a name='L13'></a><a href='#L13'>13</a>
|
|
||||||
<a name='L14'></a><a href='#L14'>14</a>
|
|
||||||
<a name='L15'></a><a href='#L15'>15</a>
|
|
||||||
<a name='L16'></a><a href='#L16'>16</a>
|
|
||||||
<a name='L17'></a><a href='#L17'>17</a>
|
|
||||||
<a name='L18'></a><a href='#L18'>18</a>
|
|
||||||
<a name='L19'></a><a href='#L19'>19</a>
|
|
||||||
<a name='L20'></a><a href='#L20'>20</a>
|
|
||||||
<a name='L21'></a><a href='#L21'>21</a>
|
|
||||||
<a name='L22'></a><a href='#L22'>22</a>
|
|
||||||
<a name='L23'></a><a href='#L23'>23</a>
|
|
||||||
<a name='L24'></a><a href='#L24'>24</a>
|
|
||||||
<a name='L25'></a><a href='#L25'>25</a>
|
|
||||||
<a name='L26'></a><a href='#L26'>26</a>
|
|
||||||
<a name='L27'></a><a href='#L27'>27</a>
|
|
||||||
<a name='L28'></a><a href='#L28'>28</a>
|
|
||||||
<a name='L29'></a><a href='#L29'>29</a>
|
|
||||||
<a name='L30'></a><a href='#L30'>30</a>
|
|
||||||
<a name='L31'></a><a href='#L31'>31</a>
|
|
||||||
<a name='L32'></a><a href='#L32'>32</a>
|
|
||||||
<a name='L33'></a><a href='#L33'>33</a>
|
|
||||||
<a name='L34'></a><a href='#L34'>34</a>
|
|
||||||
<a name='L35'></a><a href='#L35'>35</a>
|
|
||||||
<a name='L36'></a><a href='#L36'>36</a>
|
|
||||||
<a name='L37'></a><a href='#L37'>37</a>
|
|
||||||
<a name='L38'></a><a href='#L38'>38</a>
|
|
||||||
<a name='L39'></a><a href='#L39'>39</a>
|
|
||||||
<a name='L40'></a><a href='#L40'>40</a>
|
|
||||||
<a name='L41'></a><a href='#L41'>41</a>
|
|
||||||
<a name='L42'></a><a href='#L42'>42</a>
|
|
||||||
<a name='L43'></a><a href='#L43'>43</a>
|
|
||||||
<a name='L44'></a><a href='#L44'>44</a>
|
|
||||||
<a name='L45'></a><a href='#L45'>45</a>
|
|
||||||
<a name='L46'></a><a href='#L46'>46</a>
|
|
||||||
<a name='L47'></a><a href='#L47'>47</a>
|
|
||||||
<a name='L48'></a><a href='#L48'>48</a>
|
|
||||||
<a name='L49'></a><a href='#L49'>49</a>
|
|
||||||
<a name='L50'></a><a href='#L50'>50</a>
|
|
||||||
<a name='L51'></a><a href='#L51'>51</a>
|
|
||||||
<a name='L52'></a><a href='#L52'>52</a>
|
|
||||||
<a name='L53'></a><a href='#L53'>53</a>
|
|
||||||
<a name='L54'></a><a href='#L54'>54</a>
|
|
||||||
<a name='L55'></a><a href='#L55'>55</a>
|
|
||||||
<a name='L56'></a><a href='#L56'>56</a>
|
|
||||||
<a name='L57'></a><a href='#L57'>57</a>
|
|
||||||
<a name='L58'></a><a href='#L58'>58</a>
|
|
||||||
<a name='L59'></a><a href='#L59'>59</a>
|
|
||||||
<a name='L60'></a><a href='#L60'>60</a>
|
|
||||||
<a name='L61'></a><a href='#L61'>61</a>
|
|
||||||
<a name='L62'></a><a href='#L62'>62</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">/**
|
|
||||||
* 이 파일이 하는 일:
|
|
||||||
* 다음(카카오) 우편번호 서비스를 불러와 주소 검색 UI를 띄운다.
|
|
||||||
* 회원가입·내 정보의 [주소찾기] 버튼이 이걸 쓴다.
|
|
||||||
*
|
|
||||||
* 학습 포인트 ① — 왜 직접 입력 대신 주소 검색인가?
|
|
||||||
* 주소는 "형식이 정해진 데이터"다. 손으로 치게 하면 시/군/구 누락, 오타, 옛 지번주소가
|
|
||||||
* 뒤섞여 들어온다(실제로 그랬다). 검색으로 받으면 항상 표준 도로명주소 + 우편번호가 들어온다.
|
|
||||||
* — 사용자의 실수는 사용자를 탓할 게 아니라 실수할 수 없는 UI로 막는 것이다.
|
|
||||||
*
|
|
||||||
* 학습 포인트 ② — 외부 스크립트 지연 로딩(lazy loading).
|
|
||||||
* 이 스크립트는 주소찾기를 누르는 순간에만 필요하다. 앱 시작 때 미리 받으면
|
|
||||||
* 모든 페이지가 그만큼 느려진다. 그래서 버튼을 눌렀을 때 <script>를 만들어 붙이고,
|
|
||||||
* 한 번 받았으면 Promise를 재사용한다(두 번 다운로드하지 않는다).
|
|
||||||
*
|
|
||||||
* 학습 포인트 ③ — 팝업이 아닌 embed 방식.
|
|
||||||
* window.open 팝업은 모바일 브라우저가 차단하기도 한다. embed는 우리가 준 div 안에
|
|
||||||
* 검색 화면을 그려 넣는 방식이라 차단당할 게 없고, 모바일에서도 자연스럽다.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const SCRIPT_SRC = <span class="cstat-no" title="statement not covered" >'https://t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js';</span>
|
|
||||||
|
|
||||||
let scriptPromise = <span class="cstat-no" title="statement not covered" >null;</span>
|
|
||||||
|
|
||||||
function <span class="fstat-no" title="function not covered" >loadScript(</span>) {
|
|
||||||
<span class="cstat-no" title="statement not covered" > if (!scriptPromise) {</span>
|
|
||||||
<span class="cstat-no" title="statement not covered" > scriptPromise = new Promise(<span class="fstat-no" title="function not covered" >(r</span>esolve, reject) => {</span>
|
|
||||||
const script = <span class="cstat-no" title="statement not covered" >document.createElement('script');</span>
|
|
||||||
<span class="cstat-no" title="statement not covered" > script.src = SCRIPT_SRC;</span>
|
|
||||||
<span class="cstat-no" title="statement not covered" > script.onload = <span class="fstat-no" title="function not covered" >()</span> => <span class="cstat-no" title="statement not covered" >resolve(window.daum);</span></span>
|
|
||||||
<span class="cstat-no" title="statement not covered" > script.onerror = <span class="fstat-no" title="function not covered" >()</span> => {</span>
|
|
||||||
<span class="cstat-no" title="statement not covered" > scriptPromise = null; </span>// 실패하면 다음 클릭 때 다시 시도할 수 있게 초기화
|
|
||||||
<span class="cstat-no" title="statement not covered" > reject(new Error('우편번호 서비스 로딩 실패'));</span>
|
|
||||||
};
|
|
||||||
<span class="cstat-no" title="statement not covered" > document.head.appendChild(script);</span>
|
|
||||||
});
|
|
||||||
}
|
|
||||||
<span class="cstat-no" title="statement not covered" > return scriptPromise;</span>
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 주소 검색 화면을 container(div) 안에 펼친다.
|
|
||||||
*
|
|
||||||
* @param {HTMLElement} container 검색 UI를 그릴 div
|
|
||||||
* @param {(address: string) => void} onSelect 사용자가 주소를 고르면 완성된 주소 문자열을 받는다
|
|
||||||
* @returns {Promise<void>} 스크립트 로딩 실패 시 reject — 호출한 쪽에서 안내를 띄운다
|
|
||||||
*/
|
|
||||||
export async function <span class="fstat-no" title="function not covered" >embedPostcode(</span>container, onSelect) {
|
|
||||||
const daum = <span class="cstat-no" title="statement not covered" >await loadScript();</span>
|
|
||||||
<span class="cstat-no" title="statement not covered" > container.innerHTML = ''; </span>// 두 번째 열 때 이전 화면이 겹치지 않게 비운다
|
|
||||||
<span class="cstat-no" title="statement not covered" > new daum.Postcode({</span>
|
|
||||||
oncomplete: <span class="fstat-no" title="function not covered" >(d</span>ata) => {
|
|
||||||
// 도로명주소를 기본으로, 건물명이 있으면 괄호로 붙인다. 우편번호는 맨 앞에.
|
|
||||||
// 예: "[06978] 서울 동작구 상도로17길 14-2 (OO빌딩)"
|
|
||||||
const building = <span class="cstat-no" title="statement not covered" >data.buildingName ? ` (${data.buildingName})` : '';</span>
|
|
||||||
<span class="cstat-no" title="statement not covered" > onSelect(`[${data.zonecode}] ${data.roadAddress}${building}`);</span>
|
|
||||||
},
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
}).embed(container);
|
|
||||||
}
|
|
||||||
</pre></td></tr></table></pre>
|
|
||||||
|
|
||||||
<div class='push'></div><!-- for sticky footer -->
|
|
||||||
</div><!-- /wrapper -->
|
|
||||||
<div class='footer quiet pad2 space-top1 center small'>
|
|
||||||
Code coverage generated by
|
|
||||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
|
||||||
at 2026-07-17T08:57:03.899Z
|
|
||||||
</div>
|
|
||||||
<script src="prettify.js"></script>
|
|
||||||
<script>
|
|
||||||
window.onload = function () {
|
|
||||||
prettyPrint();
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
<script src="sorter.js"></script>
|
|
||||||
<script src="block-navigation.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
@ -1 +0,0 @@
|
|||||||
.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee}
|
|
||||||
File diff suppressed because one or more lines are too long
@ -1,460 +0,0 @@
|
|||||||
|
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<title>Code coverage report for runJs.js</title>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<link rel="stylesheet" href="prettify.css" />
|
|
||||||
<link rel="stylesheet" href="base.css" />
|
|
||||||
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<style type='text/css'>
|
|
||||||
.coverage-summary .sorter {
|
|
||||||
background-image: url(sort-arrow-sprite.png);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div class='wrapper'>
|
|
||||||
<div class='pad1'>
|
|
||||||
<h1><a href="index.html">All files</a> runJs.js</h1>
|
|
||||||
<div class='clearfix'>
|
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
|
||||||
<span class="strong">0% </span>
|
|
||||||
<span class="quiet">Statements</span>
|
|
||||||
<span class='fraction'>0/21</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
|
||||||
<span class="strong">0% </span>
|
|
||||||
<span class="quiet">Branches</span>
|
|
||||||
<span class='fraction'>0/2</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
|
||||||
<span class="strong">0% </span>
|
|
||||||
<span class="quiet">Functions</span>
|
|
||||||
<span class='fraction'>0/6</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
|
||||||
<span class="strong">0% </span>
|
|
||||||
<span class="quiet">Lines</span>
|
|
||||||
<span class='fraction'>0/18</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<p class="quiet">
|
|
||||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
|
||||||
</p>
|
|
||||||
<template id="filterTemplate">
|
|
||||||
<div class="quiet">
|
|
||||||
Filter:
|
|
||||||
<input type="search" id="fileSearch">
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div class='status-line low'></div>
|
|
||||||
<pre><table class="coverage">
|
|
||||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
|
||||||
<a name='L2'></a><a href='#L2'>2</a>
|
|
||||||
<a name='L3'></a><a href='#L3'>3</a>
|
|
||||||
<a name='L4'></a><a href='#L4'>4</a>
|
|
||||||
<a name='L5'></a><a href='#L5'>5</a>
|
|
||||||
<a name='L6'></a><a href='#L6'>6</a>
|
|
||||||
<a name='L7'></a><a href='#L7'>7</a>
|
|
||||||
<a name='L8'></a><a href='#L8'>8</a>
|
|
||||||
<a name='L9'></a><a href='#L9'>9</a>
|
|
||||||
<a name='L10'></a><a href='#L10'>10</a>
|
|
||||||
<a name='L11'></a><a href='#L11'>11</a>
|
|
||||||
<a name='L12'></a><a href='#L12'>12</a>
|
|
||||||
<a name='L13'></a><a href='#L13'>13</a>
|
|
||||||
<a name='L14'></a><a href='#L14'>14</a>
|
|
||||||
<a name='L15'></a><a href='#L15'>15</a>
|
|
||||||
<a name='L16'></a><a href='#L16'>16</a>
|
|
||||||
<a name='L17'></a><a href='#L17'>17</a>
|
|
||||||
<a name='L18'></a><a href='#L18'>18</a>
|
|
||||||
<a name='L19'></a><a href='#L19'>19</a>
|
|
||||||
<a name='L20'></a><a href='#L20'>20</a>
|
|
||||||
<a name='L21'></a><a href='#L21'>21</a>
|
|
||||||
<a name='L22'></a><a href='#L22'>22</a>
|
|
||||||
<a name='L23'></a><a href='#L23'>23</a>
|
|
||||||
<a name='L24'></a><a href='#L24'>24</a>
|
|
||||||
<a name='L25'></a><a href='#L25'>25</a>
|
|
||||||
<a name='L26'></a><a href='#L26'>26</a>
|
|
||||||
<a name='L27'></a><a href='#L27'>27</a>
|
|
||||||
<a name='L28'></a><a href='#L28'>28</a>
|
|
||||||
<a name='L29'></a><a href='#L29'>29</a>
|
|
||||||
<a name='L30'></a><a href='#L30'>30</a>
|
|
||||||
<a name='L31'></a><a href='#L31'>31</a>
|
|
||||||
<a name='L32'></a><a href='#L32'>32</a>
|
|
||||||
<a name='L33'></a><a href='#L33'>33</a>
|
|
||||||
<a name='L34'></a><a href='#L34'>34</a>
|
|
||||||
<a name='L35'></a><a href='#L35'>35</a>
|
|
||||||
<a name='L36'></a><a href='#L36'>36</a>
|
|
||||||
<a name='L37'></a><a href='#L37'>37</a>
|
|
||||||
<a name='L38'></a><a href='#L38'>38</a>
|
|
||||||
<a name='L39'></a><a href='#L39'>39</a>
|
|
||||||
<a name='L40'></a><a href='#L40'>40</a>
|
|
||||||
<a name='L41'></a><a href='#L41'>41</a>
|
|
||||||
<a name='L42'></a><a href='#L42'>42</a>
|
|
||||||
<a name='L43'></a><a href='#L43'>43</a>
|
|
||||||
<a name='L44'></a><a href='#L44'>44</a>
|
|
||||||
<a name='L45'></a><a href='#L45'>45</a>
|
|
||||||
<a name='L46'></a><a href='#L46'>46</a>
|
|
||||||
<a name='L47'></a><a href='#L47'>47</a>
|
|
||||||
<a name='L48'></a><a href='#L48'>48</a>
|
|
||||||
<a name='L49'></a><a href='#L49'>49</a>
|
|
||||||
<a name='L50'></a><a href='#L50'>50</a>
|
|
||||||
<a name='L51'></a><a href='#L51'>51</a>
|
|
||||||
<a name='L52'></a><a href='#L52'>52</a>
|
|
||||||
<a name='L53'></a><a href='#L53'>53</a>
|
|
||||||
<a name='L54'></a><a href='#L54'>54</a>
|
|
||||||
<a name='L55'></a><a href='#L55'>55</a>
|
|
||||||
<a name='L56'></a><a href='#L56'>56</a>
|
|
||||||
<a name='L57'></a><a href='#L57'>57</a>
|
|
||||||
<a name='L58'></a><a href='#L58'>58</a>
|
|
||||||
<a name='L59'></a><a href='#L59'>59</a>
|
|
||||||
<a name='L60'></a><a href='#L60'>60</a>
|
|
||||||
<a name='L61'></a><a href='#L61'>61</a>
|
|
||||||
<a name='L62'></a><a href='#L62'>62</a>
|
|
||||||
<a name='L63'></a><a href='#L63'>63</a>
|
|
||||||
<a name='L64'></a><a href='#L64'>64</a>
|
|
||||||
<a name='L65'></a><a href='#L65'>65</a>
|
|
||||||
<a name='L66'></a><a href='#L66'>66</a>
|
|
||||||
<a name='L67'></a><a href='#L67'>67</a>
|
|
||||||
<a name='L68'></a><a href='#L68'>68</a>
|
|
||||||
<a name='L69'></a><a href='#L69'>69</a>
|
|
||||||
<a name='L70'></a><a href='#L70'>70</a>
|
|
||||||
<a name='L71'></a><a href='#L71'>71</a>
|
|
||||||
<a name='L72'></a><a href='#L72'>72</a>
|
|
||||||
<a name='L73'></a><a href='#L73'>73</a>
|
|
||||||
<a name='L74'></a><a href='#L74'>74</a>
|
|
||||||
<a name='L75'></a><a href='#L75'>75</a>
|
|
||||||
<a name='L76'></a><a href='#L76'>76</a>
|
|
||||||
<a name='L77'></a><a href='#L77'>77</a>
|
|
||||||
<a name='L78'></a><a href='#L78'>78</a>
|
|
||||||
<a name='L79'></a><a href='#L79'>79</a>
|
|
||||||
<a name='L80'></a><a href='#L80'>80</a>
|
|
||||||
<a name='L81'></a><a href='#L81'>81</a>
|
|
||||||
<a name='L82'></a><a href='#L82'>82</a>
|
|
||||||
<a name='L83'></a><a href='#L83'>83</a>
|
|
||||||
<a name='L84'></a><a href='#L84'>84</a>
|
|
||||||
<a name='L85'></a><a href='#L85'>85</a>
|
|
||||||
<a name='L86'></a><a href='#L86'>86</a>
|
|
||||||
<a name='L87'></a><a href='#L87'>87</a>
|
|
||||||
<a name='L88'></a><a href='#L88'>88</a>
|
|
||||||
<a name='L89'></a><a href='#L89'>89</a>
|
|
||||||
<a name='L90'></a><a href='#L90'>90</a>
|
|
||||||
<a name='L91'></a><a href='#L91'>91</a>
|
|
||||||
<a name='L92'></a><a href='#L92'>92</a>
|
|
||||||
<a name='L93'></a><a href='#L93'>93</a>
|
|
||||||
<a name='L94'></a><a href='#L94'>94</a>
|
|
||||||
<a name='L95'></a><a href='#L95'>95</a>
|
|
||||||
<a name='L96'></a><a href='#L96'>96</a>
|
|
||||||
<a name='L97'></a><a href='#L97'>97</a>
|
|
||||||
<a name='L98'></a><a href='#L98'>98</a>
|
|
||||||
<a name='L99'></a><a href='#L99'>99</a>
|
|
||||||
<a name='L100'></a><a href='#L100'>100</a>
|
|
||||||
<a name='L101'></a><a href='#L101'>101</a>
|
|
||||||
<a name='L102'></a><a href='#L102'>102</a>
|
|
||||||
<a name='L103'></a><a href='#L103'>103</a>
|
|
||||||
<a name='L104'></a><a href='#L104'>104</a>
|
|
||||||
<a name='L105'></a><a href='#L105'>105</a>
|
|
||||||
<a name='L106'></a><a href='#L106'>106</a>
|
|
||||||
<a name='L107'></a><a href='#L107'>107</a>
|
|
||||||
<a name='L108'></a><a href='#L108'>108</a>
|
|
||||||
<a name='L109'></a><a href='#L109'>109</a>
|
|
||||||
<a name='L110'></a><a href='#L110'>110</a>
|
|
||||||
<a name='L111'></a><a href='#L111'>111</a>
|
|
||||||
<a name='L112'></a><a href='#L112'>112</a>
|
|
||||||
<a name='L113'></a><a href='#L113'>113</a>
|
|
||||||
<a name='L114'></a><a href='#L114'>114</a>
|
|
||||||
<a name='L115'></a><a href='#L115'>115</a>
|
|
||||||
<a name='L116'></a><a href='#L116'>116</a>
|
|
||||||
<a name='L117'></a><a href='#L117'>117</a>
|
|
||||||
<a name='L118'></a><a href='#L118'>118</a>
|
|
||||||
<a name='L119'></a><a href='#L119'>119</a>
|
|
||||||
<a name='L120'></a><a href='#L120'>120</a>
|
|
||||||
<a name='L121'></a><a href='#L121'>121</a>
|
|
||||||
<a name='L122'></a><a href='#L122'>122</a>
|
|
||||||
<a name='L123'></a><a href='#L123'>123</a>
|
|
||||||
<a name='L124'></a><a href='#L124'>124</a>
|
|
||||||
<a name='L125'></a><a href='#L125'>125</a>
|
|
||||||
<a name='L126'></a><a href='#L126'>126</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">/**
|
|
||||||
* 이 파일이 하는 일:
|
|
||||||
* 학생이 작성한 JavaScript 코드를 "안전하게" 실행하고 테스트케이스로 채점한다.
|
|
||||||
*
|
|
||||||
* 핵심 아이디어 — Web Worker.
|
|
||||||
* 학습 포인트: 브라우저에서 남의 코드를 그냥 eval()로 돌리면 두 가지가 위험하다.
|
|
||||||
* 1) while(true) 같은 무한루프가 들어오면 화면이 통째로 얼어붙는다(멈출 방법이 없다).
|
|
||||||
* 2) 그 코드가 document를 만져 화면을 부수거나 쿠키를 훔쳐볼 수 있다.
|
|
||||||
* Web Worker는 "다른 스레드에서 도는 별도의 방"이다.
|
|
||||||
* 1) 오래 걸리면 terminate()로 방을 통째로 없애 버릴 수 있다 → 무한루프 방어
|
|
||||||
* 2) Worker 안에는 document도 window도 없다 → 화면을 만질 수 없다
|
|
||||||
* 그래서 서버에 Docker 샌드박스를 짓지 않고도, 브라우저가 이미 가진 격리 장치를 빌려 쓴다.
|
|
||||||
*
|
|
||||||
* ⚠️ 이 안전함이 성립하는 조건 — 반드시 지킬 것.
|
|
||||||
* Worker는 완벽한 감옥이 아니다. document는 없지만 fetch는 되고, 같은 출처 API를
|
|
||||||
* "그 브라우저의 세션 쿠키를 달고" 호출할 수 있다.
|
|
||||||
* 지금 안전한 이유는 단 하나 — "자기 코드를 자기 브라우저에서 자기 권한으로만" 돌리기 때문이다.
|
|
||||||
* (실행 지점은 CodingProblemPage 한 곳뿐이고, 코드는 서버가 본인 것만 내려준다.)
|
|
||||||
*
|
|
||||||
* 그러므로 절대 하지 말 것:
|
|
||||||
* ❌ 멘토 화면에 "학생 코드 실행해 보기" 버튼 붙이기
|
|
||||||
* → 학생이 fetch('/api/mentor/...')를 심어두면 멘토 세션으로 실행된다 = 권한 탈취.
|
|
||||||
* 남의 코드를 남의 브라우저에서 돌려야 한다면, 그때는 서버 샌드박스가 필요하다.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/** 코드 한 번 실행에 허용할 시간(ms). 넘으면 무한루프로 보고 Worker를 없앤다. */
|
|
||||||
const TIMEOUT_MS = <span class="cstat-no" title="statement not covered" >2000;</span>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Worker 안에서 돌 코드.
|
|
||||||
* 학습 포인트: Worker는 별도 파일이 필요한데, 파일을 따로 두는 대신
|
|
||||||
* 문자열을 Blob으로 만들어 URL처럼 쓴다(createObjectURL).
|
|
||||||
* "파일 하나 늘리지 않고 Worker 쓰기" 관용구다.
|
|
||||||
*/
|
|
||||||
const workerSource = <span class="cstat-no" title="statement not covered" >`</span>
|
|
||||||
self.onmessage = function (event) {
|
|
||||||
var code = event.data.code;
|
|
||||||
var functionName = event.data.functionName;
|
|
||||||
var tests = event.data.tests;
|
|
||||||
var results = [];
|
|
||||||
|
|
||||||
// 학생 코드를 평가해서 함수를 꺼낸다.
|
|
||||||
var fn;
|
|
||||||
try {
|
|
||||||
// 학습 포인트: new Function(코드문자열)은 그 코드를 담은 함수를 만들어 준다.
|
|
||||||
// 뒤에 return을 덧붙여, 학생이 정의한 함수를 밖으로 꺼내오게 했다.
|
|
||||||
// eval()과 달리 바깥 지역 변수에 접근하지 못해(전역 스코프에서 평가된다)
|
|
||||||
// 우리 채점 코드의 변수를 학생 코드가 건드릴 수 없다.
|
|
||||||
var factory = new Function(code + '\\nreturn typeof ' + functionName + ' === "function" ? ' + functionName + ' : null;');
|
|
||||||
fn = factory();
|
|
||||||
} catch (e) {
|
|
||||||
self.postMessage({ error: '코드에 문법 오류가 있습니다: ' + e.message });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!fn) {
|
|
||||||
self.postMessage({ error: '"' + functionName + '" 함수를 찾을 수 없습니다. 함수 이름을 바꾸지 마세요.' });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < tests.length; i++) {
|
|
||||||
var test = tests[i];
|
|
||||||
var actual;
|
|
||||||
var errorMessage = null;
|
|
||||||
try {
|
|
||||||
// 인자를 복사해서 넘긴다 — 학생 코드가 배열을 바꿔도 다음 테스트에 영향이 없도록.
|
|
||||||
var args = JSON.parse(JSON.stringify(test.args));
|
|
||||||
actual = fn.apply(null, args);
|
|
||||||
} catch (e) {
|
|
||||||
errorMessage = e.message;
|
|
||||||
}
|
|
||||||
|
|
||||||
var expectedText = JSON.stringify(test.expected);
|
|
||||||
var actualText = errorMessage ? null : JSON.stringify(actual);
|
|
||||||
// 학습 포인트: 배열/객체는 ===로 비교하면 "내용이 같아도" false다(주소를 비교하니까).
|
|
||||||
// JSON 문자열로 바꿔 비교하면 내용 비교가 된다. 키 순서까지 같아야 하는 한계는 있지만
|
|
||||||
// 우리 문제 범위에서는 충분하다.
|
|
||||||
var passed = !errorMessage && actualText === expectedText;
|
|
||||||
|
|
||||||
results.push({
|
|
||||||
name: test.name,
|
|
||||||
passed: passed,
|
|
||||||
expected: expectedText,
|
|
||||||
actual: errorMessage ? ('에러: ' + errorMessage) : (actualText === undefined ? 'undefined' : actualText)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
self.postMessage({ results: results });
|
|
||||||
};
|
|
||||||
`;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 학생 코드를 테스트케이스로 채점한다.
|
|
||||||
*
|
|
||||||
* @param {string} code 학생이 작성한 코드
|
|
||||||
* @param {string} functionName 호출할 함수 이름
|
|
||||||
* @param {Array} tests [{name, args, expected}]
|
|
||||||
* @returns {Promise<{results?: Array, error?: string}>}
|
|
||||||
*/
|
|
||||||
export function <span class="fstat-no" title="function not covered" >runJs(</span>code, functionName, tests) {
|
|
||||||
<span class="cstat-no" title="statement not covered" > return new Promise(<span class="fstat-no" title="function not covered" >(r</span>esolve) => {</span>
|
|
||||||
const blob = <span class="cstat-no" title="statement not covered" >new Blob([workerSource], { type: 'application/javascript' });</span>
|
|
||||||
const url = <span class="cstat-no" title="statement not covered" >URL.createObjectURL(blob);</span>
|
|
||||||
const worker = <span class="cstat-no" title="statement not covered" >new Worker(url);</span>
|
|
||||||
|
|
||||||
// 끝났든 시간이 지났든, 뒷정리는 한 곳에서 한다.
|
|
||||||
let settled = <span class="cstat-no" title="statement not covered" >false;</span>
|
|
||||||
function <span class="fstat-no" title="function not covered" >finish(</span>value) {
|
|
||||||
<span class="cstat-no" title="statement not covered" > if (settled) <span class="cstat-no" title="statement not covered" >return;</span></span>
|
|
||||||
<span class="cstat-no" title="statement not covered" > settled = true;</span>
|
|
||||||
<span class="cstat-no" title="statement not covered" > clearTimeout(timer);</span>
|
|
||||||
<span class="cstat-no" title="statement not covered" > worker.terminate();</span>
|
|
||||||
<span class="cstat-no" title="statement not covered" > URL.revokeObjectURL(url);</span>
|
|
||||||
<span class="cstat-no" title="statement not covered" > resolve(value);</span>
|
|
||||||
}
|
|
||||||
|
|
||||||
const timer = <span class="cstat-no" title="statement not covered" >setTimeout(<span class="fstat-no" title="function not covered" >()</span> => {</span>
|
|
||||||
<span class="cstat-no" title="statement not covered" > finish({ error: `실행이 ${TIMEOUT_MS / 1000}초를 넘겼습니다. 무한루프가 아닌지 확인해 보세요.` });</span>
|
|
||||||
}, TIMEOUT_MS);
|
|
||||||
|
|
||||||
<span class="cstat-no" title="statement not covered" > worker.onmessage = <span class="fstat-no" title="function not covered" >(e</span>vent) => <span class="cstat-no" title="statement not covered" >finish(event.data);</span></span>
|
|
||||||
<span class="cstat-no" title="statement not covered" > worker.onerror = <span class="fstat-no" title="function not covered" >(e</span>vent) => <span class="cstat-no" title="statement not covered" >finish({ error: '실행 중 오류: ' + event.message });</span></span>
|
|
||||||
|
|
||||||
<span class="cstat-no" title="statement not covered" > worker.postMessage({ code, functionName, tests });</span>
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</pre></td></tr></table></pre>
|
|
||||||
|
|
||||||
<div class='push'></div><!-- for sticky footer -->
|
|
||||||
</div><!-- /wrapper -->
|
|
||||||
<div class='footer quiet pad2 space-top1 center small'>
|
|
||||||
Code coverage generated by
|
|
||||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
|
||||||
at 2026-07-17T08:57:03.899Z
|
|
||||||
</div>
|
|
||||||
<script src="prettify.js"></script>
|
|
||||||
<script>
|
|
||||||
window.onload = function () {
|
|
||||||
prettyPrint();
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
<script src="sorter.js"></script>
|
|
||||||
<script src="block-navigation.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
@ -1,358 +0,0 @@
|
|||||||
|
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<title>Code coverage report for runSql.js</title>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<link rel="stylesheet" href="prettify.css" />
|
|
||||||
<link rel="stylesheet" href="base.css" />
|
|
||||||
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<style type='text/css'>
|
|
||||||
.coverage-summary .sorter {
|
|
||||||
background-image: url(sort-arrow-sprite.png);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div class='wrapper'>
|
|
||||||
<div class='pad1'>
|
|
||||||
<h1><a href="index.html">All files</a> runSql.js</h1>
|
|
||||||
<div class='clearfix'>
|
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
|
||||||
<span class="strong">46.42% </span>
|
|
||||||
<span class="quiet">Statements</span>
|
|
||||||
<span class='fraction'>13/28</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
|
||||||
<span class="strong">50% </span>
|
|
||||||
<span class="quiet">Branches</span>
|
|
||||||
<span class='fraction'>4/8</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
|
||||||
<span class="strong">57.14% </span>
|
|
||||||
<span class="quiet">Functions</span>
|
|
||||||
<span class='fraction'>4/7</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
|
||||||
<span class="strong">41.66% </span>
|
|
||||||
<span class="quiet">Lines</span>
|
|
||||||
<span class='fraction'>10/24</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<p class="quiet">
|
|
||||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
|
||||||
</p>
|
|
||||||
<template id="filterTemplate">
|
|
||||||
<div class="quiet">
|
|
||||||
Filter:
|
|
||||||
<input type="search" id="fileSearch">
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div class='status-line low'></div>
|
|
||||||
<pre><table class="coverage">
|
|
||||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
|
||||||
<a name='L2'></a><a href='#L2'>2</a>
|
|
||||||
<a name='L3'></a><a href='#L3'>3</a>
|
|
||||||
<a name='L4'></a><a href='#L4'>4</a>
|
|
||||||
<a name='L5'></a><a href='#L5'>5</a>
|
|
||||||
<a name='L6'></a><a href='#L6'>6</a>
|
|
||||||
<a name='L7'></a><a href='#L7'>7</a>
|
|
||||||
<a name='L8'></a><a href='#L8'>8</a>
|
|
||||||
<a name='L9'></a><a href='#L9'>9</a>
|
|
||||||
<a name='L10'></a><a href='#L10'>10</a>
|
|
||||||
<a name='L11'></a><a href='#L11'>11</a>
|
|
||||||
<a name='L12'></a><a href='#L12'>12</a>
|
|
||||||
<a name='L13'></a><a href='#L13'>13</a>
|
|
||||||
<a name='L14'></a><a href='#L14'>14</a>
|
|
||||||
<a name='L15'></a><a href='#L15'>15</a>
|
|
||||||
<a name='L16'></a><a href='#L16'>16</a>
|
|
||||||
<a name='L17'></a><a href='#L17'>17</a>
|
|
||||||
<a name='L18'></a><a href='#L18'>18</a>
|
|
||||||
<a name='L19'></a><a href='#L19'>19</a>
|
|
||||||
<a name='L20'></a><a href='#L20'>20</a>
|
|
||||||
<a name='L21'></a><a href='#L21'>21</a>
|
|
||||||
<a name='L22'></a><a href='#L22'>22</a>
|
|
||||||
<a name='L23'></a><a href='#L23'>23</a>
|
|
||||||
<a name='L24'></a><a href='#L24'>24</a>
|
|
||||||
<a name='L25'></a><a href='#L25'>25</a>
|
|
||||||
<a name='L26'></a><a href='#L26'>26</a>
|
|
||||||
<a name='L27'></a><a href='#L27'>27</a>
|
|
||||||
<a name='L28'></a><a href='#L28'>28</a>
|
|
||||||
<a name='L29'></a><a href='#L29'>29</a>
|
|
||||||
<a name='L30'></a><a href='#L30'>30</a>
|
|
||||||
<a name='L31'></a><a href='#L31'>31</a>
|
|
||||||
<a name='L32'></a><a href='#L32'>32</a>
|
|
||||||
<a name='L33'></a><a href='#L33'>33</a>
|
|
||||||
<a name='L34'></a><a href='#L34'>34</a>
|
|
||||||
<a name='L35'></a><a href='#L35'>35</a>
|
|
||||||
<a name='L36'></a><a href='#L36'>36</a>
|
|
||||||
<a name='L37'></a><a href='#L37'>37</a>
|
|
||||||
<a name='L38'></a><a href='#L38'>38</a>
|
|
||||||
<a name='L39'></a><a href='#L39'>39</a>
|
|
||||||
<a name='L40'></a><a href='#L40'>40</a>
|
|
||||||
<a name='L41'></a><a href='#L41'>41</a>
|
|
||||||
<a name='L42'></a><a href='#L42'>42</a>
|
|
||||||
<a name='L43'></a><a href='#L43'>43</a>
|
|
||||||
<a name='L44'></a><a href='#L44'>44</a>
|
|
||||||
<a name='L45'></a><a href='#L45'>45</a>
|
|
||||||
<a name='L46'></a><a href='#L46'>46</a>
|
|
||||||
<a name='L47'></a><a href='#L47'>47</a>
|
|
||||||
<a name='L48'></a><a href='#L48'>48</a>
|
|
||||||
<a name='L49'></a><a href='#L49'>49</a>
|
|
||||||
<a name='L50'></a><a href='#L50'>50</a>
|
|
||||||
<a name='L51'></a><a href='#L51'>51</a>
|
|
||||||
<a name='L52'></a><a href='#L52'>52</a>
|
|
||||||
<a name='L53'></a><a href='#L53'>53</a>
|
|
||||||
<a name='L54'></a><a href='#L54'>54</a>
|
|
||||||
<a name='L55'></a><a href='#L55'>55</a>
|
|
||||||
<a name='L56'></a><a href='#L56'>56</a>
|
|
||||||
<a name='L57'></a><a href='#L57'>57</a>
|
|
||||||
<a name='L58'></a><a href='#L58'>58</a>
|
|
||||||
<a name='L59'></a><a href='#L59'>59</a>
|
|
||||||
<a name='L60'></a><a href='#L60'>60</a>
|
|
||||||
<a name='L61'></a><a href='#L61'>61</a>
|
|
||||||
<a name='L62'></a><a href='#L62'>62</a>
|
|
||||||
<a name='L63'></a><a href='#L63'>63</a>
|
|
||||||
<a name='L64'></a><a href='#L64'>64</a>
|
|
||||||
<a name='L65'></a><a href='#L65'>65</a>
|
|
||||||
<a name='L66'></a><a href='#L66'>66</a>
|
|
||||||
<a name='L67'></a><a href='#L67'>67</a>
|
|
||||||
<a name='L68'></a><a href='#L68'>68</a>
|
|
||||||
<a name='L69'></a><a href='#L69'>69</a>
|
|
||||||
<a name='L70'></a><a href='#L70'>70</a>
|
|
||||||
<a name='L71'></a><a href='#L71'>71</a>
|
|
||||||
<a name='L72'></a><a href='#L72'>72</a>
|
|
||||||
<a name='L73'></a><a href='#L73'>73</a>
|
|
||||||
<a name='L74'></a><a href='#L74'>74</a>
|
|
||||||
<a name='L75'></a><a href='#L75'>75</a>
|
|
||||||
<a name='L76'></a><a href='#L76'>76</a>
|
|
||||||
<a name='L77'></a><a href='#L77'>77</a>
|
|
||||||
<a name='L78'></a><a href='#L78'>78</a>
|
|
||||||
<a name='L79'></a><a href='#L79'>79</a>
|
|
||||||
<a name='L80'></a><a href='#L80'>80</a>
|
|
||||||
<a name='L81'></a><a href='#L81'>81</a>
|
|
||||||
<a name='L82'></a><a href='#L82'>82</a>
|
|
||||||
<a name='L83'></a><a href='#L83'>83</a>
|
|
||||||
<a name='L84'></a><a href='#L84'>84</a>
|
|
||||||
<a name='L85'></a><a href='#L85'>85</a>
|
|
||||||
<a name='L86'></a><a href='#L86'>86</a>
|
|
||||||
<a name='L87'></a><a href='#L87'>87</a>
|
|
||||||
<a name='L88'></a><a href='#L88'>88</a>
|
|
||||||
<a name='L89'></a><a href='#L89'>89</a>
|
|
||||||
<a name='L90'></a><a href='#L90'>90</a>
|
|
||||||
<a name='L91'></a><a href='#L91'>91</a>
|
|
||||||
<a name='L92'></a><a href='#L92'>92</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-yes">1x</span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-no"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-yes">29x</span>
|
|
||||||
<span class="cline-any cline-yes">9x</span>
|
|
||||||
<span class="cline-any cline-yes">9x</span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-yes">9x</span>
|
|
||||||
<span class="cline-any cline-yes">1x</span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-yes">8x</span>
|
|
||||||
<span class="cline-any cline-yes">5x</span>
|
|
||||||
<span class="cline-any cline-yes">5x</span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-yes">11x</span>
|
|
||||||
<span class="cline-any cline-neutral"> </span>
|
|
||||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">/**
|
|
||||||
* 이 파일이 하는 일:
|
|
||||||
* 학생이 작성한 SQL을 브라우저 안에서 실행하고, 기대 결과와 비교해 채점한다.
|
|
||||||
*
|
|
||||||
* 핵심 아이디어 — sql.js (SQLite를 WebAssembly로 컴파일한 것).
|
|
||||||
* 학습 포인트: "SQL을 실행한다"고 하면 서버 DB부터 떠올리기 쉽지만, 그건 위험하다.
|
|
||||||
* 학생 SQL에 DROP TABLE이 들어 있으면? 서버가 학생마다 DB를 만들어 줘야 하나?
|
|
||||||
* sql.js는 진짜 SQLite 엔진을 브라우저 안에서 돌린다. 문제를 열 때마다
|
|
||||||
* 메모리 위에 1회용 DB를 새로 만들고, 닫으면 사라진다.
|
|
||||||
* → 무엇을 실행하든 서버에는 아무 일도 일어나지 않는다. DROP도 DELETE도 자유.
|
|
||||||
* → runJs.js(Web Worker)와 같은 철학이다: "남의 코드는 그 사람의 브라우저에서만".
|
|
||||||
*
|
|
||||||
* JS 채점(runJs.js)과 달리 Web Worker를 쓰지 않는 이유:
|
|
||||||
* SELECT는 코드가 아니라 질의라서 무한루프를 만들기가 어렵고(재귀 CTE 정도가 예외),
|
|
||||||
* 데이터가 몇 줄 안 돼 실행이 순식간이다. Worker로 감싸는 복잡함이 이득보다 크다.
|
|
||||||
* — 같은 문제(격리)라도 위험의 크기에 따라 장치의 무게를 달리한다.
|
|
||||||
*/
|
|
||||||
import initSqlJs from 'sql.js';
|
|
||||||
// 학습 포인트: ?url 은 Vite 문법 — "이 파일을 번들에 포함하고, 그 주소(URL)를 달라".
|
|
||||||
// WASM은 JS처럼 import할 수 없어서, 주소를 sql.js에 알려 주고 직접 내려받게 한다.
|
|
||||||
import sqlWasmUrl from 'sql.js/dist/sql-wasm.wasm?url';
|
|
||||||
|
|
||||||
/** WASM 로딩은 느리다(수백 KB) — 한 번만 하고 계속 재사용한다(캐싱). */
|
|
||||||
let sqlJsPromise = null;
|
|
||||||
|
|
||||||
function <span class="fstat-no" title="function not covered" >loadSqlJs(</span>) {
|
|
||||||
<span class="cstat-no" title="statement not covered" > if (!sqlJsPromise) {</span>
|
|
||||||
<span class="cstat-no" title="statement not covered" > sqlJsPromise = initSqlJs({ locateFile: <span class="fstat-no" title="function not covered" >()</span> => <span class="cstat-no" title="statement not covered" >sqlWasmUrl </span>});</span>
|
|
||||||
}
|
|
||||||
<span class="cstat-no" title="statement not covered" > return sqlJsPromise;</span>
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 준비 SQL(테이블+데이터)을 깐 1회용 DB에서 학생 쿼리를 실행한다.
|
|
||||||
*
|
|
||||||
* @param {string} setupSql CREATE TABLE + INSERT (문제가 제공)
|
|
||||||
* @param {string} query 학생이 작성한 SQL
|
|
||||||
* @returns {Promise<{columns?: string[], rows?: Array[], error?: string}>}
|
|
||||||
*/
|
|
||||||
export async function <span class="fstat-no" title="function not covered" >runSql(</span>setupSql, query) {
|
|
||||||
const SQL = <span class="cstat-no" title="statement not covered" >await loadSqlJs();</span>
|
|
||||||
const db = <span class="cstat-no" title="statement not covered" >new SQL.Database();</span> // 메모리 위 1회용 DB — 새 것으로 시작
|
|
||||||
<span class="cstat-no" title="statement not covered" > try {</span>
|
|
||||||
<span class="cstat-no" title="statement not covered" > db.run(setupSql);</span>
|
|
||||||
// exec는 [{columns, values}] 배열을 돌려준다. 문장이 여러 개면 결과도 여러 개인데,
|
|
||||||
// 채점은 "마지막 SELECT의 결과"로 한다 — 학생이 확인용 문장을 앞에 써도 괜찮도록.
|
|
||||||
const results = <span class="cstat-no" title="statement not covered" >db.exec(query);</span>
|
|
||||||
<span class="cstat-no" title="statement not covered" > if (results.length === 0) {</span>
|
|
||||||
<span class="cstat-no" title="statement not covered" > return { error: '결과가 없습니다. SELECT 문을 작성했는지 확인해 보세요.' };</span>
|
|
||||||
}
|
|
||||||
const last = <span class="cstat-no" title="statement not covered" >results[results.length - 1];</span>
|
|
||||||
<span class="cstat-no" title="statement not covered" > return { columns: last.columns, rows: last.values };</span>
|
|
||||||
} catch (e) {
|
|
||||||
// SQLite의 에러 메시지는 영어지만 원문 그대로 보여준다 —
|
|
||||||
// "near \"FORM\": syntax error" 같은 메시지를 읽어내는 것도 배워야 할 기술이다.
|
|
||||||
<span class="cstat-no" title="statement not covered" > return { error: 'SQL 오류: ' + e.message };</span>
|
|
||||||
} finally {
|
|
||||||
<span class="cstat-no" title="statement not covered" > db.close(); </span>// WASM 메모리는 가비지 컬렉터가 못 치운다 — 반드시 직접 닫는다.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 실행 결과를 기대 결과와 비교한다.
|
|
||||||
*
|
|
||||||
* 채점 규칙:
|
|
||||||
* - 값만 비교한다. 컬럼 "이름"은 비교하지 않는다 — SELECT salary AS 연봉 처럼
|
|
||||||
* 별칭을 붙여도 정답이다. (컬럼 개수가 다르면 행 내용이 달라져 어차피 틀린다)
|
|
||||||
* - orderMatters가 false면 행 순서는 무시한다. ORDER BY를 요구하는 문제만 true로 둔다.
|
|
||||||
*
|
|
||||||
* 학습 포인트: 행 순서를 무시하는 비교는 "정렬해서 비교"로 구현한다.
|
|
||||||
* 두 결과를 같은 규칙으로 정렬해 놓으면, 내용이 같은지 순서와 무관하게 확인할 수 있다.
|
|
||||||
*
|
|
||||||
* @param {{columns: string[], rows: Array[]}} actual 학생 쿼리의 실행 결과
|
|
||||||
* @param {{expectedRows: Array[], orderMatters: boolean}} expected 문제의 기대 결과
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
export function gradeSql(actual, expected) {
|
|
||||||
// 행을 JSON 문자열로 바꿔 비교한다 — runJs.js의 배열 비교와 같은 요령이다.
|
|
||||||
const normalize = (rows) => rows.map((row) => JSON.stringify(row));
|
|
||||||
const actualRows = normalize(actual.rows);
|
|
||||||
const expectedRows = normalize(expected.expectedRows);
|
|
||||||
|
|
||||||
if (actualRows.length !== expectedRows.length) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!expected.orderMatters) {
|
|
||||||
actualRows.sort();
|
|
||||||
expectedRows.sort();
|
|
||||||
}
|
|
||||||
return actualRows.every((row, i) => row === expectedRows[i]);
|
|
||||||
}
|
|
||||||
</pre></td></tr></table></pre>
|
|
||||||
|
|
||||||
<div class='push'></div><!-- for sticky footer -->
|
|
||||||
</div><!-- /wrapper -->
|
|
||||||
<div class='footer quiet pad2 space-top1 center small'>
|
|
||||||
Code coverage generated by
|
|
||||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
|
||||||
at 2026-07-17T08:57:03.899Z
|
|
||||||
</div>
|
|
||||||
<script src="prettify.js"></script>
|
|
||||||
<script>
|
|
||||||
window.onload = function () {
|
|
||||||
prettyPrint();
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
<script src="sorter.js"></script>
|
|
||||||
<script src="block-navigation.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 138 B |
@ -1,210 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
var addSorting = (function() {
|
|
||||||
'use strict';
|
|
||||||
var cols,
|
|
||||||
currentSort = {
|
|
||||||
index: 0,
|
|
||||||
desc: false
|
|
||||||
};
|
|
||||||
|
|
||||||
// returns the summary table element
|
|
||||||
function getTable() {
|
|
||||||
return document.querySelector('.coverage-summary');
|
|
||||||
}
|
|
||||||
// returns the thead element of the summary table
|
|
||||||
function getTableHeader() {
|
|
||||||
return getTable().querySelector('thead tr');
|
|
||||||
}
|
|
||||||
// returns the tbody element of the summary table
|
|
||||||
function getTableBody() {
|
|
||||||
return getTable().querySelector('tbody');
|
|
||||||
}
|
|
||||||
// returns the th element for nth column
|
|
||||||
function getNthColumn(n) {
|
|
||||||
return getTableHeader().querySelectorAll('th')[n];
|
|
||||||
}
|
|
||||||
|
|
||||||
function onFilterInput() {
|
|
||||||
const searchValue = document.getElementById('fileSearch').value;
|
|
||||||
const rows = document.getElementsByTagName('tbody')[0].children;
|
|
||||||
|
|
||||||
// Try to create a RegExp from the searchValue. If it fails (invalid regex),
|
|
||||||
// it will be treated as a plain text search
|
|
||||||
let searchRegex;
|
|
||||||
try {
|
|
||||||
searchRegex = new RegExp(searchValue, 'i'); // 'i' for case-insensitive
|
|
||||||
} catch (error) {
|
|
||||||
searchRegex = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < rows.length; i++) {
|
|
||||||
const row = rows[i];
|
|
||||||
let isMatch = false;
|
|
||||||
|
|
||||||
if (searchRegex) {
|
|
||||||
// If a valid regex was created, use it for matching
|
|
||||||
isMatch = searchRegex.test(row.textContent);
|
|
||||||
} else {
|
|
||||||
// Otherwise, fall back to the original plain text search
|
|
||||||
isMatch = row.textContent
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchValue.toLowerCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
row.style.display = isMatch ? '' : 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// loads the search box
|
|
||||||
function addSearchBox() {
|
|
||||||
var template = document.getElementById('filterTemplate');
|
|
||||||
var templateClone = template.content.cloneNode(true);
|
|
||||||
templateClone.getElementById('fileSearch').oninput = onFilterInput;
|
|
||||||
template.parentElement.appendChild(templateClone);
|
|
||||||
}
|
|
||||||
|
|
||||||
// loads all columns
|
|
||||||
function loadColumns() {
|
|
||||||
var colNodes = getTableHeader().querySelectorAll('th'),
|
|
||||||
colNode,
|
|
||||||
cols = [],
|
|
||||||
col,
|
|
||||||
i;
|
|
||||||
|
|
||||||
for (i = 0; i < colNodes.length; i += 1) {
|
|
||||||
colNode = colNodes[i];
|
|
||||||
col = {
|
|
||||||
key: colNode.getAttribute('data-col'),
|
|
||||||
sortable: !colNode.getAttribute('data-nosort'),
|
|
||||||
type: colNode.getAttribute('data-type') || 'string'
|
|
||||||
};
|
|
||||||
cols.push(col);
|
|
||||||
if (col.sortable) {
|
|
||||||
col.defaultDescSort = col.type === 'number';
|
|
||||||
colNode.innerHTML =
|
|
||||||
colNode.innerHTML + '<span class="sorter"></span>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cols;
|
|
||||||
}
|
|
||||||
// attaches a data attribute to every tr element with an object
|
|
||||||
// of data values keyed by column name
|
|
||||||
function loadRowData(tableRow) {
|
|
||||||
var tableCols = tableRow.querySelectorAll('td'),
|
|
||||||
colNode,
|
|
||||||
col,
|
|
||||||
data = {},
|
|
||||||
i,
|
|
||||||
val;
|
|
||||||
for (i = 0; i < tableCols.length; i += 1) {
|
|
||||||
colNode = tableCols[i];
|
|
||||||
col = cols[i];
|
|
||||||
val = colNode.getAttribute('data-value');
|
|
||||||
if (col.type === 'number') {
|
|
||||||
val = Number(val);
|
|
||||||
}
|
|
||||||
data[col.key] = val;
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
// loads all row data
|
|
||||||
function loadData() {
|
|
||||||
var rows = getTableBody().querySelectorAll('tr'),
|
|
||||||
i;
|
|
||||||
|
|
||||||
for (i = 0; i < rows.length; i += 1) {
|
|
||||||
rows[i].data = loadRowData(rows[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// sorts the table using the data for the ith column
|
|
||||||
function sortByIndex(index, desc) {
|
|
||||||
var key = cols[index].key,
|
|
||||||
sorter = function(a, b) {
|
|
||||||
a = a.data[key];
|
|
||||||
b = b.data[key];
|
|
||||||
return a < b ? -1 : a > b ? 1 : 0;
|
|
||||||
},
|
|
||||||
finalSorter = sorter,
|
|
||||||
tableBody = document.querySelector('.coverage-summary tbody'),
|
|
||||||
rowNodes = tableBody.querySelectorAll('tr'),
|
|
||||||
rows = [],
|
|
||||||
i;
|
|
||||||
|
|
||||||
if (desc) {
|
|
||||||
finalSorter = function(a, b) {
|
|
||||||
return -1 * sorter(a, b);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < rowNodes.length; i += 1) {
|
|
||||||
rows.push(rowNodes[i]);
|
|
||||||
tableBody.removeChild(rowNodes[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
rows.sort(finalSorter);
|
|
||||||
|
|
||||||
for (i = 0; i < rows.length; i += 1) {
|
|
||||||
tableBody.appendChild(rows[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// removes sort indicators for current column being sorted
|
|
||||||
function removeSortIndicators() {
|
|
||||||
var col = getNthColumn(currentSort.index),
|
|
||||||
cls = col.className;
|
|
||||||
|
|
||||||
cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, '');
|
|
||||||
col.className = cls;
|
|
||||||
}
|
|
||||||
// adds sort indicators for current column being sorted
|
|
||||||
function addSortIndicators() {
|
|
||||||
getNthColumn(currentSort.index).className += currentSort.desc
|
|
||||||
? ' sorted-desc'
|
|
||||||
: ' sorted';
|
|
||||||
}
|
|
||||||
// adds event listeners for all sorter widgets
|
|
||||||
function enableUI() {
|
|
||||||
var i,
|
|
||||||
el,
|
|
||||||
ithSorter = function ithSorter(i) {
|
|
||||||
var col = cols[i];
|
|
||||||
|
|
||||||
return function() {
|
|
||||||
var desc = col.defaultDescSort;
|
|
||||||
|
|
||||||
if (currentSort.index === i) {
|
|
||||||
desc = !currentSort.desc;
|
|
||||||
}
|
|
||||||
sortByIndex(i, desc);
|
|
||||||
removeSortIndicators();
|
|
||||||
currentSort.index = i;
|
|
||||||
currentSort.desc = desc;
|
|
||||||
addSortIndicators();
|
|
||||||
};
|
|
||||||
};
|
|
||||||
for (i = 0; i < cols.length; i += 1) {
|
|
||||||
if (cols[i].sortable) {
|
|
||||||
// add the click event handler on the th so users
|
|
||||||
// dont have to click on those tiny arrows
|
|
||||||
el = getNthColumn(i).querySelector('.sorter').parentElement;
|
|
||||||
if (el.addEventListener) {
|
|
||||||
el.addEventListener('click', ithSorter(i));
|
|
||||||
} else {
|
|
||||||
el.attachEvent('onclick', ithSorter(i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// adds sorting functionality to the UI
|
|
||||||
return function() {
|
|
||||||
if (!getTable()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cols = loadColumns();
|
|
||||||
loadData();
|
|
||||||
addSearchBox();
|
|
||||||
addSortIndicators();
|
|
||||||
enableUI();
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
||||||
window.addEventListener('load', addSorting);
|
|
||||||
Loading…
x
Reference in New Issue
Block a user