Vitest/Jest Fake Timers로 시간 제어

describe('time-dependent tests', () => {
  beforeEach(() => {
    vi.useFakeTimers() // jest.useFakeTimers('modern')
  })

  afterEach(() => {
    vi.useRealTimers() // jest.useRealTimers()
  })

  it('특정 시간에 동작 확인', () => {
    vi.setSystemTime(new Date(2000, 1, 1, 13)) // 13시 설정
    expect(purchase()).toEqual({ message: 'Success' })
  })

  it('영업시간 외 동작', () => {
    vi.setSystemTime(new Date(2000, 1, 1, 19)) // 19시 설정
    expect(purchase()).toEqual({ message: 'Error' })
  })
})

@sinonjs/fake-timers 기반. Date, setTimeout 등 모킹.

#347

두 개의 오버로딩된 메서드에서 각각의 반환 타입을 명시적으로 추출하기

type Year = {
  year(): number
  year(u: string): string
}

type ReturnTo<T, R> = T extends R ? R : never

type GetYear = ReturnTo<Year['year'], () => number>
type SetYear = ReturnTo<Year['year'], (u: string) => string>
#345

DFS (깊이 우선 탐색)

// 재귀 방식
function dfs(graph, node, visited = new Set()) {
  visited.add(node)
  console.log(node)
  graph[node].forEach((neighbor) => {
    if (!visited.has(neighbor)) dfs(graph, neighbor, visited)
  })
}

// 스택 방식
function dfsStack(graph, startNode) {
  const stack = [startNode]
  const visited = new Set()

  while (stack.length > 0) {
    const node = stack.pop()

    if (!visited.has(node)) {
      console.log(node)
      visited.add(node)
      graph[node]
        .slice()
        .reverse()
        .forEach((neighbor) => {
          if (!visited.has(neighbor)) stack.push(neighbor)
        })
    }
  }
}

const graph = {
  0: [1, 2],
  1: [0, 3, 4],
  2: [0, 5],
  3: [1],
  4: [1],
  5: [2],
}
dfs(graph, 0) // 0, 1, 3, 4, 2, 5
#344

JavaScript delete 연산자

const obj = { name: 'Alice', age: 25 }
delete obj.age // true

// 존재하지 않는 속성 삭제도 true
delete obj.city // true

// configurable: false는 삭제 불가
const locked = Object.defineProperty({}, 'readOnly', {
  value: 'I cannot be deleted',
  configurable: false,
})
delete locked.readOnly // false

// 전역 변수 삭제 불가
let globalVar = 'exists'
delete globalVar // false

// 배열 요소 삭제 (hole 생성)
let arr = [1, 2, 3]
delete arr[1] // [1, <empty>, 3]
  • 설정 가능한(configurable) 속성에만 사용
  • 배열은 splice 권장
#343
  • 문제: 스프레드시트 데이터가 커서 내용 확인이 어려움.
  • 해결: 구글 드라이브에 스프레드시트 데이터를 CSV 파일로 업로드하여 확인.
/**
 * 현재 활성 스프레드시트의 첫 번째 시트 데이터를 CSV 파일로 저장하고,
 * 구글 드라이브에 업로드합니다.
 * 생성된 파일의 URL은 콘솔에 로그로 출력됩니다.
 */
function saveSpreadsheetDataAsCSV() {
  /** 현재 활성 스프레드시트 */
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0]
  /** 시트의 모든 데이터의 2차원 배열 */
  const data = sheet.getDataRange().getValues()
  /** CSV 데이터 */
  const csv = data.map((row) => row.join(',')).join('\n')
  /** 파일명 */
  const fileName = 'spreadsheet_data.csv'
  /** 구글 드라이브에 업로드 완료된 CSV 파일 */
  const file = DriveApp.createFile(fileName, csv, MimeType.CSV)

  console.log('파일이 생성되었습니다: ' + file.getUrl())
}
#342
class ColorManipulator {
  private baseColor: string
  private targetColor: string

  constructor(baseColor: string, targetColor: string) {
    this.baseColor = baseColor
    this.targetColor = targetColor
  }

  private hexToRgb(hex: string) {
    const bigint = parseInt(hex.slice(1), 16)

    return {
      r: (bigint >> 16) & 255,
      g: (bigint >> 8) & 255,
      b: bigint & 255,
    }
  }

  public calculateOpacity() {
    const target = this.hexToRgb(this.targetColor)
    const baseRG = this.hexToRgb(this.baseColor)
    const opacities = [
      (target.r - baseRG.r) / (255 - baseRG.r),
      (target.g - baseRG.g) / (255 - baseRG.g),
      (target.b - baseRG.b) / (255 - baseRG.b),
    ]
    const averageOpacity =
      opacities.reduce((sum, value) => sum + value, 0) / opacities.length

    return averageOpacity
  }

  public getCssRGBA() {
    const opacity = this.calculateOpacity()

    return `rgba(0, 0, 0, ${opacity.toFixed(2)})`
  }
}

const manipulator = new ColorManipulator('#000000', '#D1D7DE')
const opacity = manipulator.calculateOpacity()
const cssRGBA = manipulator.getCssRGBA()
#341

구글 스프레드시트에서 중복 값을 찾고 그 값에 별도의 스타일을 적용하는 방법

=COUNTIF(A:A, A:A) > 1
  1. 셀 범위 선택: 중복 값을 확인하고 스타일을 적용할 셀 범위를 선택합니다. 예를 들어, A 열 전체를 선택하려면 A 열을 클릭합니다.

  2. 조건부 서식 열기: 상단 메뉴에서 **“서식”**을 클릭한 다음 **“조건부 서식”**을 선택합니다.

  3. 서식 규칙 설정: 조건부 서식 규칙 창이 열리면, “서식 규칙 추가” 옵션을 클릭합니다.

  4. 맞춤 수식 사용: 서식 규칙 드롭다운에서 **“맞춤 수식을 사용하여 서식 지정”**을 선택합니다.

  5. 중복 값 조건 수식 입력: 중복을 찾는 수식을 입력합니다. 이 수식은 A 열 전체에서 A1과 같은 값을 가지는 셀의 개수를 세고, 그 개수가 1보다 크면 중복으로 간주합니다.

  6. 서식 선택: 스타일을 지정하려면 서식 스타일에서 텍스트 색상, 배경 색상 등을 원하는 대로 설정합니다.

  7. 완료: 설정이 완료되면 완료를 클릭하여 적용합니다.

#340

Google Spreadsheet 팝업

function showAlert() {
  const ui = SpreadsheetApp.getUi()
  ui.alert('팝업 창', '표시할 텍스트', ui.ButtonSet.OK)
}

// 메뉴에 추가
function onOpen() {
  SpreadsheetApp.getUi()
    .createMenu('팝업 메뉴')
    .addItem('팝업 띄우기', 'showAlert')
    .addToUi()
}
#337

특수 기호 제거

function removeSpecialCharacters(input) {
  return input.replace(/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]+/g, '')
}

removeSpecialCharacters('Hello_World123! 안녕하세요?')
// "HelloWorld123 안녕하세요"
#336

조건부 렌더링을 한다고 했을때 예전에는 주로 B로 처리했던 것 같은데 디버깅 때문에 고생해서 그런지 생각이 바뀌었다.

function renderA() {
  return <Item isPacked={true} name="Space suit" />
}

function renderB() {
  return <>{isPacked ? <Item name="Space suit" /> : null}</>
}

조건부 렌더링을 보다 간결하게 표현하기 위해 If, Then, 그리고 Else 컴포넌트 개념을 차용하는 방법. 이 컴포넌트는 If에서 condition prop을 통해 조건을 받아들이고, 자식으로 ThenElse를 받아 각각의 내용을 렌더링하도록 한다.

type Props = {
  /** 렌더링할 조건 */
  condition: boolean
  /** 자식 컴포넌트 (Then, Else 포함) */
  children: React.ReactNode
}

/**
 * If 컴포넌트는 조건부 렌더링을 위한 컴포넌트
 */
function If({ condition, children }: Props) {
  let thenChild = null
  let elseChild = null

  React.Children.forEach(children, (child) => {
    if (!React.isValidElement(child)) return
    if (child.type === Then) thenChild = child
    if (child.type === Else) elseChild = child
  })

  return condition ? thenChild : elseChild
}

/**
 * Then, Else 컴포넌트는 조건이 참 또는 거짓일 때 렌더링되는 콘텐츠를 포함
 */
const Then = ({ children }: React.PropsWithChildren) => <>{children}</>
const Else = ({ children }: React.PropsWithChildren) => <>{children}</>
#335
25 중 8페이지