diff --git a/.gitignore b/.gitignore deleted file mode 100644 index efc3745..0000000 --- a/.gitignore +++ /dev/null @@ -1,15 +0,0 @@ -### Intellij ### -# User-specific stuff -.idea/ - -### Jekyll ### -_site/ -.sass-cache/ -.jekyll-cache/ -.jekyll-metadata - -### macOS ### -# General -.DS_Store -.AppleDouble -.LSOverride \ No newline at end of file diff --git a/1/index.html b/1/index.html new file mode 100644 index 0000000..141ad1e --- /dev/null +++ b/1/index.html @@ -0,0 +1,130 @@ +ATDD 프로세스 :: 맨땅에 코딩

ATDD 프로세스

29 November 2019 — Written by Boorownie
#ATDD#Agile

ATDD(Acceptance Test Driven Development)는, 용어를 그대로 풀어서 해석하면, 인수 테스트 주도 개발으로서 +인수 테스트를 먼저 작성한 다음 기능 개발을 하는 방법입니다. +여기서 말하는 인수는 클라이언트가 요청한 소프트웨어의 결과물을 넘겨 받는다는 의미이며, 인수 테스트는 요구사항을 모두 만족하여 기능 완료 여부를 확인하는 테스트라고 할 수 있습니다.

+

단순히 인수 테스트를 만들기 위해 ATDD를 도입하는 것은 아닙니다. +ATDD의 뜻을 찾아보면 사용자(고객)-개발자-테스터간의 커뮤니케이션을 기반한 개발 방법이라고 정의됩니다. +커뮤니케이션을 위해서 인수 테스트를 만든다는 뜻인데 이 둘의 연관관계를 연상하는 것이 어려울 수 있습니다. +이번 포스팅에서는 인수 테스트를 만드는 ATDD의 궁극적인 목표와 ATDD 프로세스를 예시를 통해 알아보도록 하겠습니다.

+

애자일 관점에서의 ATDD

+ +

애자일 방법론에서는 미리 예측하며 개발을 하지 않습니다. 일정한 주기를 가지고 끊임없이 프로토 타입을 만들어냅니다. +필요한 부분이 발생하면, 그때 기능 목록을 추가하고 수정하여 하나의 커다란 소프트웨어를 개발해 나갑니다.[link] +이러한 애자일의 프로그밍 방법론 중 하나인 ATDD는 사용자 스토리를 기반으로 +인수 조건을 도출하여 +기능 개발을 진행하는 방법론 입니다.

+

+

ATDD에서는 사용자(고객)-개발자-테스터, 즉 팀원들이 각 포지션의 관점으로 함께 논의하여 실행 가능한 예제를 도출합니다. +이러한 과정은 사용자(고객)들에게 요구사항을 명확히 하는데 도움을 주고, 개발자에게는 코드 구현의 방향성과 목적을 가지는데 도움을 주고, 테스터에게는 단순히 기능적 테스트를 하는 것 보다 좀 더 나은 계획을 세울 수 있게 도와줍니다. +결국 ATDD는 팀원간에 요구사항을 같은 수준으로 이해하는데 큰 도움을 줍니다. +요구사항이 무엇이고 기능 완료 기준이 무엇인지에 대해 하나의 공통된 생각을 가질 수 있게 합니다. +일정한 주기를 가지고 진행하는 "스프린트"에서는 이러한 공통의 기준들이 매우 중요합니다.[link]

+
+

즉, 스프린트에서는 팀원들이 공통의 기준을 가지고 진행하는것이 중요합니다. 사용자 스토리와 인수 조건, 인수 테스트 작성을 모든 개발 팀원이 함께 논의하여 요구사항에 대한 공통의 이해를 할 수 있게 도와줍니다.

+
+

ATDD 프로세스

+

+

ATDD는 4가지의 행위(Discuss, Distill, Develop, Demo)와 +4가지의 산출물(User Story, Acceptance Criteria, Tests, Working Software), +하나의 결과(Business Value)로 설명할 수 있습니다.[link] +ATDD Cycle을 바탕으로 서핑 용품 대여 서비스를 개발해 나가는 과정을 따라가며 ATDD 프로세스에 대해서 알아보겠습니다.

+

사용자 스토리 작성하기

+
    +
  • 사용자 스토리는 who, why, what을 기준으로 작성합니다.[link]
  • +
  • +

    결과물: 사용자 스토리

    +
    +

    서핑샵 손님들은 서핑 용품을 빌리기 위해 대여 신청을 하고 싶다.

    +
    +
  • +
+

Discuss

+
    +
  • 사용자 스토리를 바탕으로 예시만들고, 이를 통해 인수 조건을 도출합니다. 인수 조건 잘 도출하는 법
  • +
  • 인수 조건은 기능 완료 조건으로서 사용자 스토리 보다 조금 더 구체적인 시나리오를 통해 기술합니다.
  • +
  • +

    결과물: 인수 조건

    +
    +
      +
    • 날짜별로 대여가 가능한 용품을 조회할 수 있어야 한다.
    • +
    • 대여를 희망하는 용품을 희망하는 날짜에 신청 할 수 있어야 한다.
    • +
    • 대여 신청한 내역을 사장님이 확인 할 수 있어야 한다.
    • +
    +
    +
  • +
+

Distill

+
    +
  • 도출된 인수 조건을 통해 인수 테스트를 작성합니다.
  • +
  • 인수 테스트는 사용자의 관점을 나타내며 시스템 작동 방식을 설명하기 위한 요구 사항의 형태로 작동하며 시스템이 의도 한대로 작동하는지 확인하는 방법으로도 사용됩니다.
  • +
  • 테스트는 Given-When-Then 형식으로 작성을 하되 해당사항이 없는 부분은 생략할 수 있습니다.
  • +
  • 성공 케이스와 실패 케이스를 정의하고 요청/응답에 대한 DTO를 결정합니다.
  • +
  • 인수 테스트 작성의 기준은 팀에서 정합니다. (ex. 인수 테스트는 기능별로 만들고 Controller 테스트를 통해 에러 케이스를 검증한다.)
  • +
  • +

    결과물: 인수 테스트

    +
    @DisplayName("날짜별로 대여가 가능한 용품을 조회할 수 있어야 한다.")
    +@Test
    +public void showItems() {
    + this.webTestClient.get()
    +         .uri(uriBuilder -> uriBuilder.path("/items")
    +                 .queryParam("date", "20191127")
    +                 .build())
    +         .accept(MediaType.APPLICATION_JSON)
    +         .exchange()
    +         .expectStatus().isOk()
    +         .expectHeader().contentType(MediaType.APPLICATION_JSON)
    +         .expectBody()
    +         .jsonPath("$").isNotEmpty();
    +}
    +
  • +
+

테스트 시나리오의 품질관리

+

+기술적인 행위를 바탕으로 시나리오를 작성 할 경우 기술과 관련된 용어가 나타나고 구현과 관련된 기술이 바뀌거나 시스템을 사용하는 순서가 바뀌면 테스트 시나리오를 수정해야 합니다. +작업 흐름을 바탕으로 시나리오를 작성 할 경우 기술이 바뀌더라도 테스트 시나리오가 바뀌지 않지만, 작업 흐름이 변경되면 테스트 시나리오를 수정해야 합니다. +비즈니스 규칙을 명확히 드러나도록 테스트 시나리오를 작성하면 구현과 관련 기술이 변경되어도, 작업 흐름이 바뀌어도 테스트 시나리오를 변경하지 않아도 됩니다. +[6]

+
Technical Activity
+
    +
  1. 대여 가능한 용품을 조회할 수 있는 페이지로 접속한다.
  2. +
  3. 조회하고자 하는 날짜를 "20191127"이라고 입력한다.
  4. +
  5. 조회하기 버튼을 누른다.
  6. +
  7. 결과 페이지에서 대여가능한 용품 목록을 확인한다.
  8. +
+
Workflow
+
    +
  1. 대여 가능한 용품을 조회할 수 있는 페이지로 접속한다.
  2. +
  3. 조회하고자 하는 날짜를 2019년 11월 27일로 설정한다.
  4. +
  5. 조회한다.
  6. +
  7. 대여가능한 용품 목록 확인한다.
  8. +
+
Business Rule
+
    +
  • Scenario: 특정 일자에 대여 가능한 물품 정보를 제공한다.
  • +
  • When: 2019년 11월 27일 대여 가능한 물품을 조회한다.
  • +
  • Then: 2019년 11월 27일 대여 가능한 물품 목록을 확인할 수 있다.
  • +
+

Develop

+
    +
  • +

    문서화

    +
      +
    • 벡엔드와 프론트의 작업을 병렬로 진행할 수 있도록 도움을 줍니다.
    • +
    • 문서화 할 기능의 기준은 팀에서 정합니다. (ex. 사이드 케이스 / 실패 케이스에 대해서는 내부항목으로 기재한다.)
    • +
    +
  • +
  • +

    TDD로 개발하기

    +
      +
    • 인수 테스트 기반으로 하나씩 기능개발을 진행합니다.
    • +
    • TDD 프로세스에 따라 테스트를 먼저 작성하고 프로덕션 코드를 작성합니다.
    • +
    +
  • +
  • 결과물: 소프트웨어 결과물
  • +
+

Demo

+
    +
  • 인수 테스트가 성공이 되면 해당 이슈는 완료합니다. (애자일에서는 검증 단계가 필요 없을까?)
  • +
  • 완료된 인수 테스트 수에 따라서 개발 진행 상황을 인지할 수 있습니다.
  • +
\ No newline at end of file diff --git a/2/index.html b/2/index.html new file mode 100644 index 0000000..2aef032 --- /dev/null +++ b/2/index.html @@ -0,0 +1,323 @@ +ATDD with Spring Boot :: 맨땅에 코딩

ATDD with Spring Boot

30 November 2019 — Written by Boorownie
#ATDD#Spring#Spring Boot

+

TDD를 배우면서 도메인에 먼저 집중하여 도메인 기능의 테스트 코드를 구현한 후 기능을 구현하는 방법으로 학습합니다. +한 번에 한 가지를 집중하고 각 도메인들은 애플리케이션이 구성되기 전에 작성됩니다. +하지만 ATDD에서 인수 테스트는 큰 그림을 그려 구조를 형성한 다음 구체적인 부분을 구현합니다. +이러한 접근 방법을 전자는 Inside out이라 하고 후자는 Outside in이라고 합니다.(TDD가 Inside out이고 ATDD가 Outside in이라는 뜻은 아닙니다.) +TDD를 이미 접한 후 ATDD를 배우는 사람은 각 접근 방식의 차이점을 인지하지 못하여 학습에 어려움을 겪기도 합니다. +Inside out 접근 방식은 도메인 지식이 있거나 요구 사항이 단순한 경우 적합하고 +Outside in 접근 방식은 도메인 지식이 없거나 요구 사항 복잡도가 높은 경우 적합하다 할 수 있습니다. +각 방식의 장단점은 TDD - From the Inside Out or the Outside In? 에서 확인할 수 있습니다. +이번 포스팅에서는 간단한 예제를 통해 Outside in방식의 ATDD 개발 방법에 대해서 알아보겠습니다.

+

ATDD 개발 프로세스

+
    +
  1. 인수 조건을 바탕으로 인수 테스트 작성한다.
  2. +
  3. 인수 테스트를 바탕으로 문서화를 하여 인터페이스를 공유한다.
  4. +
  5. 인수 테스트를 동작시키는 기능을 하나씩 구현한다.
  6. +
+

[예제] 서핑 용품 대여 신청 기능 개발

+
+

상황에 따라 테스트 방법이 달라질 수 있고 아래의 예시는 그 방법 중 하나입니다.

+
+

서핑 용품 대여 신청 기능을 구현하는 과정을 ATDD 개발 프로세스로 단계별로 알아보겠습니다. +예제에서는 WebTestClient로 Happy case에 대한 인수 테스트를 만들고 +예외적인 상황이나 에러 처리 테스트는 Controller Test의 MockMvc객체를 이용할 예정입니다. +(추후 Github 공유 예정)

+

인수 조건

+
+

대여를 희망하는 용품을 희망하는 날짜에 신청 할 수 있어야 한다.

+
+

인수 테스트 작성

+

이전 포스팅에서 언급되었던 것 처럼, 비즈니스 규칙(Business Rule)은 기술의 구현이나 작업흐름만큼 변경이 많지 않습니다. +인수 테스트를 작성할 때 비즈니스 규칙을 고려하여 테스트 시나리오를 만들 경우 변경사항에 비교적으로 유연하게 대처할 수 있습니다. +그런 관점에서 볼 때, UI 인수 테스트를 작성하는 것 보다 API 인수 테스트를 하는게 시나리오의 품질관리에 유리합니다. +따라서 UI 테스트 보다는 API 테스트를 통해 인수 테스트를 작성하겠습니다.

+
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@AutoConfigureWebTestClient
+public class RentalAcceptanceTest {
+    public static final String LOCATION = "Location";
+
+    @Autowired
+    private WebTestClient webTestClient;
+
+    @Test
+    public void createRental() {
+        String inputJson = "{\"dateTime\":\"" + "2019120112" + "\", " +
+                "\"itemId\":\"" + "110920" + "\", " +
+                "\"itemType\":\"" + "14" + "\"}";
+
+        this.webTestClient.post().uri("/rentals")
+                .contentType(MediaType.APPLICATION_JSON) // 요청으로 보내는 데이터 유형 명시
+                .accept(MediaType.APPLICATION_JSON) // 응답으로 받고 싶은 데이터 유형 명시
+                .body(Mono.just(inputJson), String.class)
+                .exchange()
+                .expectStatus().isCreated()
+                .expectHeader().contentType(MediaType.APPLICATION_JSON)
+                .expectHeader().valueMatches(LOCATION, "\\/rentals\\/\\d")
+                .expectBody()
+                .jsonPath("$").isNotEmpty()
+                .jsonPath("$.id").isNotEmpty()
+                .jsonPath("$.status").isEqualTo("READY");
+    }
+}
+

문서화

+

Mock 서버를 먼저 개발하여 Mock 데이터를 제공하면 백엔드와 프론트엔드 개발이 병렬적으로 진행이 가능합니다. +이 때, 인수 테스트를 기반으로 문서화까지 해서 제공한다면 더 효과적인 커뮤니케이션이 가능합니다. +따라서 Spring Rest Docs를 이용하여 문서를 만들 예정입니다. +Rest Docs를 이용하여 문서를 만들기 위해서는 테스트가 성공되어야 합니다. +따라서 인수 테스트를 성공시키기 위한 Mock 서버를 구현하고 Request / Response DTO를 선언합니다. +이 부분은 ATDD 프로세스에 위배될 수 있지만 업무 효율을 고려하여 유리하다고 판단하여 이렇게 결정하였습니다.

+

Mock 서버 구현

+
@RestController
+public class RentalController {
+    @PostMapping("/rentals")
+    public ResponseEntity createArticles(@RequestBody RentalRequestDto requestDto) {
+        return ResponseEntity.created(URI.create("/rentals/" + 1)).body(new RentalResponseDto(1, "READY"));
+    }
+}
+

Rest Docs 문서 설정

+

Rest Docs를 통해 문서화를 할 경우 다음과 같은 객체를 사용하는 것을 권장합니다, MockMvc / WebTestClient / RestAssured +각 테스트 객체의 특징을 고려하여 선택한 후 문서화에 사용하면 좋을 것 같습니다. +상황에 따라서 여러 종류의 테스트 객체를 사용해야 할 수 도 있습니다.

+

참고 문서

+ +

TDD 기능 구현

+

Controller TDD

+

Controller의 메서드에서 구현할 내용에 대해서 given을 통해 응답을 정의합니다. +작업 순서는 Controller Test를 작성하면서 Service 클래스를 생성하여 빈 껍데기 메서드만 만든 뒤 Controller 프로덕션 코드를 작성합니다. +Controller 테스트에서 MockMvc 객체를 사용한 이유는 Interceptor나 ArgumentResolver와 같이 Controller 이전에 동작하는 기능 등 전체 로직을 통합 테스트하기 위해서 입니다. +만약 Controller나 Interceptor, ArgumentResolver 와 같은 객체들을 단위테스트 할 경우, 해당 객체를 생성하여 메서드를 바로 호출하는 테스트를 작성해도 무관합니다. +RentalService의 createRental 메서드에서는 Item조회를 하고 Item이 유효한지 확인한 후 Rental을 만들어 저장하는 로직을 구현할 예정입니다.

+

아래 코드의 개발 순서는

+
@WebMvcTest(controllers = RentalController.class)
+@AutoConfigureMockMvc
+public class RentalControllerTest {
+    @Autowired
+    private MockMvc mockMvc;
+
+    // 3 - RentalService 객체를 만들고 createRental메서드 생성
+    @MockBean
+    private RentalService rentalService;
+
+    @DisplayName("대여 신청을 한다.")
+    @Test
+    public void createRental() throws Exception {
+
+        // 5 - ItemTest와 RentalTest를 생성하고 생성하는 로직을 구현
+        Item item = new Item(110920, "READY");
+        Rental rental = new Rental(1, "20191127", item, "READY");
+
+        // 2 - item 존재 여부확인 + rental 저장 로직을 service 객체로 분리하기로 결정
+        given(rentalService.createRental(any())).willReturn(rental);
+
+        // 1
+        String inputJson = "{\"date\":\"" + "20191127" + "\", " +
+                "\"itemId\":\"" + "110920" + "\"}";
+        mockMvc.perform(post("/rentals")
+                .contentType(MediaType.APPLICATION_JSON)
+                .accept(MediaType.APPLICATION_JSON)
+                .content(inputJson))
+                .andExpect(status().isCreated())
+                .andExpect(header().exists("Location"))
+                .andExpect(jsonPath("$.id").isNotEmpty())
+                .andExpect(jsonPath("$.date").value("20191127"))
+                .andExpect(jsonPath("$.itemId").value("110920"))
+                .andExpect(jsonPath("$.status").value("READY"))
+                .andDo(print());
+    }
+}
+
+// 4
+@Service
+public class RentalService {
+    public Rental createRental(RentalRequestDto requestDto) {
+        return null;
+    }
+}
+
+// 6
+@RestController
+public class RentalController {
+    private RentalService rentalService;
+
+    public RentalController(RentalService rentalService) {
+        this.rentalService = rentalService;
+    }
+
+    @PostMapping("/rentals")
+    public ResponseEntity createArticles(@RequestBody RentalRequestDto requestDto) {
+        Rental persistRental = rentalService.createRental(requestDto);
+        return ResponseEntity.created(URI.create("/rentals/" + persistRental.getId())).body(persistRental);
+    }
+}
+

Service TDD

+

Controller Test에서 given으로 정의한 Service 메서드를 구현합니다. Controller와 마찬가지로 테스트를 먼저 작성한 뒤 프로덕션 코드를 작성합니다. +Service 레이어를 테스트 할 때 반드시 Repository를 MockBean으로 간주하여 단위테스트를 해야하는 건 아니며 트랙젝션 등 처리로직 확인을 위해서는 통합테스트로 진행해도 무관합니다.

+
@SpringBootTest(classes = RentalService.class)
+public class RentalServiceTest {
+    private RentalService rentalService;
+
+    // 4
+    @MockBean
+    private RentalRepository rentalRepository;
+    @MockBean
+    private ItemRepository itemRepository;
+
+    @BeforeEach
+    void setUp() {
+        rentalService = new RentalService(rentalRepository, itemRepository);
+    }
+
+    @Test
+    void createRental() {
+        Item item = new Item(1, "READY");
+
+        // 3
+        given(itemRepository.findById(anyInt())).willReturn(item);
+
+        RentalRequestDto rentalRequestDto = new RentalRequestDto("20191127", 1123);
+
+        // 2
+        given(rentalRepository.save(any())).willReturn(new Rental(1, rentalRequestDto.getDate(), item, "READY"));
+
+        // 1
+        Rental persistRental = rentalService.createRental(rentalRequestDto);
+
+        assertThat(persistRental).isNotNull();
+    }
+}
+
+// 5
+@Service
+public class RentalService {
+    private RentalRepository rentalRepository;
+    private ItemRepository itemRepository;
+
+    public RentalService(RentalRepository rentalRepository, ItemRepository itemRepository) {
+        this.rentalRepository = rentalRepository;
+        this.itemRepository = itemRepository;
+    }
+
+    public Rental createRental(RentalRequestDto requestDto) {
+        Item persistItem = itemRepository.findById(requestDto.getItemId());
+        Rental persistRental = new Rental(requestDto.getDate(), persistItem, "READY");
+        return rentalRepository.save(persistRental);
+    }
+}
+

Repository TDD

+
@SpringBootTest(classes = RentalRepository.class)
+public class RentalRepositoryTest {
+    private RentalRepository rentalRepository;
+
+    @BeforeEach
+    void setUp() {
+        rentalRepository = new RentalRepository();
+    }
+
+    @Test
+    void save() {
+        // 2
+        Item item = new Item(110920, "READY");
+        Rental rental = new Rental("20191127", item, "READY");
+
+        // 1
+        Rental persistRental = rentalRepository.save(rental);
+
+        assertThat(persistRental.getId()).isEqualTo(1);
+    }
+}
+
+@Repository
+public class RentalRepository {
+    private List<Rental> rentals = new ArrayList<>();
+
+    public Rental save(Rental rental) {
+        Rental persistRental = new Rental(rentals.size() + 1, rental.getDate(), rental.getItem(), rental.getStatus());
+        rentals.add(persistRental);
+        return persistRental;
+    }
+}
+
// ItemRepository도 같은 방식으로 진행
+

Controller TDD - 예외 케이스

+

정상적인 로직에 대한 테스트 작성이 끝나면 Side Case에 대한 테스트를 작성해야합니다. +Happy Case에 대한 테스트를 작성하는 것 처럼 테스트 케이스에 대한 메서드를 만들어주고 given 조건과 기대하는 then 조건을 설정합니다.

+

참고로 도메인 클래스 작성은 어느 레이어에서 해도 상관이 없다고 생각합니다. +Outside in 이라고 해서 반드시 그 방향대로(ex. Controller -> Service -> Repository 순) 개발을 해야하는 것이 아니라 +테스트를 구현하고 로직을 구현하다가 필요한 로직이 생기면 그 때 해당 부분을 구현하기 위해 테스트 코드를 구현하고 로직을 구현하였습니다.

+
@DisplayName("대여 신청 시 아이템이 이미 대여중인 경우 400에러를 응답한다.")
+@Test
+public void createRentalWithInvalidItemId() throws Exception {
+    given(rentalService.createRental(any())).willThrow(new InvalidItemException());
+
+    String inputJson = "{\"date\":\"" + "20191127" + "\", " +
+            "\"itemId\":\"" + "111" + "\"}";
+
+    mockMvc.perform(post("/rentals")
+            .contentType(MediaType.APPLICATION_JSON)
+            .accept(MediaType.APPLICATION_JSON)
+            .content(inputJson))
+            .andExpect(status().isBadRequest())
+            .andDo(print());
+}
+
+@ResponseStatus(HttpStatus.BAD_REQUEST)
+public class InvalidItemException extends RuntimeException{
+}
+
+public class RentalTest {
+    @Test()
+    void checkItem() {
+        Assertions.assertThrows(AlreadyRentItemException.class, () -> {
+            new Rental("20191127", new Item(111, "RENT"), "READY");
+        });
+    }
+}
+
+public class Rental {
+    ...
+
+    public Rental(String date, Item item, String status) {
+        item.checkStatus();
+        this.date = date;
+        this.item = item;
+        this.status = status;
+    }
+
+    ...
+}
+
+public class ItemTest {
+    @Test
+    void checkStatus() {
+        Item item = new Item(111, "RENT");
+        Assertions.assertThrows(AlreadyRentItemException.class, () -> {
+            item.checkStatus();
+        });
+    }
+}
+
+
+public class Item {
+    ...
+
+    public void checkStatus() {
+        if (status.equals("RENT")) {
+            throw new AlreadyRentItemException();
+        }
+    }
+}
+

리팩터링

+

인수 테스트 동작 확인

+

TDD 프로세스를 통해 기능 구현이 끝나면 최초 작성한 인수 테스트가 정상적으로 동작하는지 확인을 합니다. +최초에 인수 테스트가 성공한 것은 Mock 서버가 동작하고 있었기 때문인데 이 기능을 실제 기능 구현으로 대체하게 되었습니다.

\ No newline at end of file diff --git a/23c3d3a3a2879427593569aafa02d04d4d5b9e2a-b2fb258daae83e93cfa3.js b/23c3d3a3a2879427593569aafa02d04d4d5b9e2a-b2fb258daae83e93cfa3.js new file mode 100644 index 0000000..758fe3e --- /dev/null +++ b/23c3d3a3a2879427593569aafa02d04d4d5b9e2a-b2fb258daae83e93cfa3.js @@ -0,0 +1,2 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[2],{"+pmV":function(e,t,a){e.exports={post:"post-module--post--28Mq2",title:"post-module--title--3XBo2",coverImage:"post-module--coverImage--1GM7V",meta:"post-module--meta--3YtjE",tags:"post-module--tags--3RbqF",tag:"post-module--tag--16U9p",series:"post-module--series--3YUjN",seriesItem:"post-module--series-item--mOT0Y",seriesItemBold:"post-module--series-item-bold--2Vyvw",readMore:"post-module--readMore--3zWML",postContent:"post-module--postContent--1bfnt"}},"6cYQ":function(e,t,a){"use strict";var r=a("q1tI"),i=a.n(r),n=a("17x9"),s=a.n(n),o=a("Wbzz"),l=a("zHTP"),d=a.n(l),c=function(e){var t=e.nextPath,a=e.previousPath,r=e.nextLabel,n=e.previousLabel;return a||t?i.a.createElement("div",{className:d.a.navigation},a&&i.a.createElement("span",{className:d.a.button},i.a.createElement(o.Link,{to:a},i.a.createElement("span",{className:d.a.iconPrev},"←"),i.a.createElement("span",{className:d.a.buttonText},n))),t&&i.a.createElement("span",{className:d.a.button},i.a.createElement(o.Link,{to:t},i.a.createElement("span",{className:d.a.buttonText},r),i.a.createElement("span",{className:d.a.iconNext},"→")))):null};c.propTypes={nextPath:s.a.string,previousPath:s.a.string,nextLabel:s.a.string,previousLabel:s.a.string},t.a=c},"9eSz":function(e,t,a){"use strict";var r=a("TqRt");t.__esModule=!0,t.default=void 0;var i,n=r(a("PJYZ")),s=r(a("VbXa")),o=r(a("8OQS")),l=r(a("pVnL")),d=r(a("q1tI")),c=r(a("17x9")),u=function(e){var t=(0,l.default)({},e),a=t.resolutions,r=t.sizes,i=t.critical;return a&&(t.fixed=a,delete t.resolutions),r&&(t.fluid=r,delete t.sizes),i&&(t.loading="eager"),t.fluid&&(t.fluid=x([].concat(t.fluid))),t.fixed&&(t.fixed=x([].concat(t.fixed))),t},f=function(e){var t=e.media;return!!t&&(b&&!!window.matchMedia(t).matches)},p=function(e){var t=e.fluid,a=e.fixed,r=m(t||a||[]);return r&&r.src},m=function(e){if(b&&function(e){return!!e&&Array.isArray(e)&&e.some((function(e){return void 0!==e.media}))}(e)){var t=e.findIndex(f);if(-1!==t)return e[t];var a=e.findIndex((function(e){return void 0===e.media}));if(-1!==a)return e[a]}return e[0]},g=Object.create({}),h=function(e){var t=u(e),a=p(t);return g[a]||!1},v="undefined"!=typeof HTMLImageElement&&"loading"in HTMLImageElement.prototype,b="undefined"!=typeof window,y=b&&window.IntersectionObserver,E=new WeakMap;function S(e){return e.map((function(e){var t=e.src,a=e.srcSet,r=e.srcSetWebp,i=e.media,n=e.sizes;return d.default.createElement(d.default.Fragment,{key:t},r&&d.default.createElement("source",{type:"image/webp",media:i,srcSet:r,sizes:n}),a&&d.default.createElement("source",{media:i,srcSet:a,sizes:n}))}))}function x(e){var t=[],a=[];return e.forEach((function(e){return(e.media?t:a).push(e)})),[].concat(t,a)}function w(e){return e.map((function(e){var t=e.src,a=e.media,r=e.tracedSVG;return d.default.createElement("source",{key:t,media:a,srcSet:r})}))}function L(e){return e.map((function(e){var t=e.src,a=e.media,r=e.base64;return d.default.createElement("source",{key:t,media:a,srcSet:r})}))}function I(e,t){var a=e.srcSet,r=e.srcSetWebp,i=e.media,n=e.sizes;return""}var R=function(e,t){var a=(void 0===i&&"undefined"!=typeof window&&window.IntersectionObserver&&(i=new window.IntersectionObserver((function(e){e.forEach((function(e){if(E.has(e.target)){var t=E.get(e.target);(e.isIntersecting||e.intersectionRatio>0)&&(i.unobserve(e.target),E.delete(e.target),t())}}))}),{rootMargin:"200px"})),i);return a&&(a.observe(e),E.set(e,t)),function(){a.unobserve(e),E.delete(e)}},N=function(e){var t=e.src?'src="'+e.src+'" ':'src="" ',a=e.sizes?'sizes="'+e.sizes+'" ':"",r=e.srcSet?'srcset="'+e.srcSet+'" ':"",i=e.title?'title="'+e.title+'" ':"",n=e.alt?'alt="'+e.alt+'" ':'alt="" ',s=e.width?'width="'+e.width+'" ':"",o=e.height?'height="'+e.height+'" ':"",l=e.crossOrigin?'crossorigin="'+e.crossOrigin+'" ':"",d=e.loading?'loading="'+e.loading+'" ':"",c=e.draggable?'draggable="'+e.draggable+'" ':"";return""+e.imageVariants.map((function(e){return(e.srcSetWebp?I(e,!0):"")+I(e)})).join("")+"'},P=d.default.forwardRef((function(e,t){var a=e.src,r=e.imageVariants,i=e.generateSources,n=e.spreadProps,s=e.ariaHidden,o=d.default.createElement(O,(0,l.default)({ref:t,src:a},n,{ariaHidden:s}));return r.length>1?d.default.createElement("picture",null,i(r),o):o})),O=d.default.forwardRef((function(e,t){var a=e.sizes,r=e.srcSet,i=e.src,n=e.style,s=e.onLoad,c=e.onError,u=e.loading,f=e.draggable,p=e.ariaHidden,m=(0,o.default)(e,["sizes","srcSet","src","style","onLoad","onError","loading","draggable","ariaHidden"]);return d.default.createElement("img",(0,l.default)({"aria-hidden":p,sizes:a,srcSet:r,src:i},m,{onLoad:s,onError:c,ref:t,loading:u,draggable:f,style:(0,l.default)({position:"absolute",top:0,left:0,width:"100%",height:"100%",objectFit:"cover",objectPosition:"center"},n)}))}));O.propTypes={style:c.default.object,onError:c.default.func,onLoad:c.default.func};var T=function(e){function t(t){var a;(a=e.call(this,t)||this).seenBefore=b&&h(t),a.isCritical="eager"===t.loading||t.critical,a.addNoScript=!(a.isCritical&&!t.fadeIn),a.useIOSupport=!v&&y&&!a.isCritical&&!a.seenBefore;var r=a.isCritical||b&&(v||!a.useIOSupport);return a.state={isVisible:r,imgLoaded:!1,imgCached:!1,fadeIn:!a.seenBefore&&t.fadeIn,isHydrated:!1},a.imageRef=d.default.createRef(),a.placeholderRef=t.placeholderRef||d.default.createRef(),a.handleImageLoaded=a.handleImageLoaded.bind((0,n.default)(a)),a.handleRef=a.handleRef.bind((0,n.default)(a)),a}(0,s.default)(t,e);var a=t.prototype;return a.componentDidMount=function(){if(this.setState({isHydrated:b}),this.state.isVisible&&"function"==typeof this.props.onStartLoad&&this.props.onStartLoad({wasCached:h(this.props)}),this.isCritical){var e=this.imageRef.current;e&&e.complete&&this.handleImageLoaded()}},a.componentWillUnmount=function(){this.cleanUpListeners&&this.cleanUpListeners()},a.handleRef=function(e){var t=this;this.useIOSupport&&e&&(this.cleanUpListeners=R(e,(function(){var e=h(t.props);t.state.isVisible||"function"!=typeof t.props.onStartLoad||t.props.onStartLoad({wasCached:e}),t.setState({isVisible:!0},(function(){t.setState({imgLoaded:e,imgCached:!(!t.imageRef.current||!t.imageRef.current.currentSrc)})}))})))},a.handleImageLoaded=function(){var e,t,a;e=this.props,t=u(e),(a=p(t))&&(g[a]=!0),this.setState({imgLoaded:!0}),this.props.onLoad&&this.props.onLoad()},a.render=function(){var e=u(this.props),t=e.title,a=e.alt,r=e.className,i=e.style,n=void 0===i?{}:i,s=e.imgStyle,o=void 0===s?{}:s,c=e.placeholderStyle,f=void 0===c?{}:c,p=e.placeholderClassName,g=e.fluid,h=e.fixed,v=e.backgroundColor,b=e.durationFadeIn,y=e.Tag,E=e.itemProp,x=e.loading,I=e.draggable,R=g||h;if(!R)return null;var T=!1===this.state.fadeIn||this.state.imgLoaded,z=!0===this.state.fadeIn&&!this.state.imgCached,k=(0,l.default)({opacity:T?1:0,transition:z?"opacity "+b+"ms":"none"},o),V="boolean"==typeof v?"lightgray":v,C={transitionDelay:b+"ms"},H=(0,l.default)({opacity:this.state.imgLoaded?0:1},z&&C,o,f),W={title:t,alt:this.state.isVisible?"":a,style:H,className:p,itemProp:E},M=this.state.isHydrated?m(R):R[0];if(g)return d.default.createElement(y,{className:(r||"")+" gatsby-image-wrapper",style:(0,l.default)({position:"relative",overflow:"hidden",maxWidth:M.maxWidth?M.maxWidth+"px":null,maxHeight:M.maxHeight?M.maxHeight+"px":null},n),ref:this.handleRef,key:"fluid-"+JSON.stringify(M.srcSet)},d.default.createElement(y,{"aria-hidden":!0,style:{width:"100%",paddingBottom:100/M.aspectRatio+"%"}}),V&&d.default.createElement(y,{"aria-hidden":!0,title:t,style:(0,l.default)({backgroundColor:V,position:"absolute",top:0,bottom:0,opacity:this.state.imgLoaded?0:1,right:0,left:0},z&&C)}),M.base64&&d.default.createElement(P,{ariaHidden:!0,ref:this.placeholderRef,src:M.base64,spreadProps:W,imageVariants:R,generateSources:L}),M.tracedSVG&&d.default.createElement(P,{ariaHidden:!0,ref:this.placeholderRef,src:M.tracedSVG,spreadProps:W,imageVariants:R,generateSources:w}),this.state.isVisible&&d.default.createElement("picture",null,S(R),d.default.createElement(O,{alt:a,title:t,sizes:M.sizes,src:M.src,crossOrigin:this.props.crossOrigin,srcSet:M.srcSet,style:k,ref:this.imageRef,onLoad:this.handleImageLoaded,onError:this.props.onError,itemProp:E,loading:x,draggable:I})),this.addNoScript&&d.default.createElement("noscript",{dangerouslySetInnerHTML:{__html:N((0,l.default)({alt:a,title:t,loading:x},M,{imageVariants:R}))}}));if(h){var j=(0,l.default)({position:"relative",overflow:"hidden",display:"inline-block",width:M.width,height:M.height},n);return"inherit"===n.display&&delete j.display,d.default.createElement(y,{className:(r||"")+" gatsby-image-wrapper",style:j,ref:this.handleRef,key:"fixed-"+JSON.stringify(M.srcSet)},V&&d.default.createElement(y,{"aria-hidden":!0,title:t,style:(0,l.default)({backgroundColor:V,width:M.width,opacity:this.state.imgLoaded?0:1,height:M.height},z&&C)}),M.base64&&d.default.createElement(P,{ariaHidden:!0,ref:this.placeholderRef,src:M.base64,spreadProps:W,imageVariants:R,generateSources:L}),M.tracedSVG&&d.default.createElement(P,{ariaHidden:!0,ref:this.placeholderRef,src:M.tracedSVG,spreadProps:W,imageVariants:R,generateSources:w}),this.state.isVisible&&d.default.createElement("picture",null,S(R),d.default.createElement(O,{alt:a,title:t,width:M.width,height:M.height,sizes:M.sizes,src:M.src,crossOrigin:this.props.crossOrigin,srcSet:M.srcSet,style:k,ref:this.imageRef,onLoad:this.handleImageLoaded,onError:this.props.onError,itemProp:E,loading:x,draggable:I})),this.addNoScript&&d.default.createElement("noscript",{dangerouslySetInnerHTML:{__html:N((0,l.default)({alt:a,title:t,loading:x},M,{imageVariants:R}))}}))}return null},t}(d.default.Component);T.defaultProps={fadeIn:!0,durationFadeIn:500,alt:"",Tag:"div",loading:"lazy"};var z=c.default.shape({width:c.default.number.isRequired,height:c.default.number.isRequired,src:c.default.string.isRequired,srcSet:c.default.string.isRequired,base64:c.default.string,tracedSVG:c.default.string,srcWebp:c.default.string,srcSetWebp:c.default.string,media:c.default.string}),k=c.default.shape({aspectRatio:c.default.number.isRequired,src:c.default.string.isRequired,srcSet:c.default.string.isRequired,sizes:c.default.string.isRequired,base64:c.default.string,tracedSVG:c.default.string,srcWebp:c.default.string,srcSetWebp:c.default.string,media:c.default.string,maxWidth:c.default.number,maxHeight:c.default.number});function V(e){return function(t,a,r){var i;if(!t.fixed&&!t.fluid)throw new Error("The prop `fluid` or `fixed` is marked as required in `"+r+"`, but their values are both `undefined`.");c.default.checkPropTypes(((i={})[a]=e,i),t,"prop",r)}}T.propTypes={resolutions:z,sizes:k,fixed:V(c.default.oneOfType([z,c.default.arrayOf(z)])),fluid:V(c.default.oneOfType([k,c.default.arrayOf(k)])),fadeIn:c.default.bool,durationFadeIn:c.default.number,title:c.default.string,alt:c.default.string,className:c.default.oneOfType([c.default.string,c.default.object]),critical:c.default.bool,crossOrigin:c.default.oneOfType([c.default.string,c.default.bool]),style:c.default.object,imgStyle:c.default.object,placeholderStyle:c.default.object,placeholderClassName:c.default.string,backgroundColor:c.default.oneOfType([c.default.string,c.default.bool]),onLoad:c.default.func,onError:c.default.func,onStartLoad:c.default.func,Tag:c.default.string,itemProp:c.default.string,loading:c.default.oneOf(["auto","lazy","eager"]),draggable:c.default.bool};var C=T;t.default=C},TWNs:function(e,t,a){var r=a("g6v/"),i=a("2oRo"),n=a("lMq5"),s=a("cVYH"),o=a("m/L8").f,l=a("JBy8").f,d=a("ROdP"),c=a("rW0t"),u=a("n3/R"),f=a("busE"),p=a("0Dky"),m=a("afO8").set,g=a("JiZb"),h=a("tiKp")("match"),v=i.RegExp,b=v.prototype,y=/a/g,E=/a/g,S=new v(y)!==y,x=u.UNSUPPORTED_Y;if(r&&n("RegExp",!S||x||p((function(){return E[h]=!1,v(y)!=y||v(E)==E||"/a/i"!=v(y,"i")})))){for(var w=function(e,t){var a,r=this instanceof w,i=d(e),n=void 0===t;if(!r&&i&&e.constructor===w&&n)return e;S?i&&!n&&(e=e.source):e instanceof w&&(n&&(t=c.call(e)),e=e.source),x&&(a=!!t&&t.indexOf("y")>-1)&&(t=t.replace(/y/g,""));var o=s(S?new v(e,t):v(e,t),r?this:b,w);return x&&a&&m(o,{sticky:a}),o},L=function(e){e in w||o(w,e,{configurable:!0,get:function(){return v[e]},set:function(t){v[e]=t}})},I=l(v),R=0;I.length>R;)L(I[R++]);b.constructor=w,w.prototype=b,f(i,"RegExp",w)}g("RegExp")},UxlC:function(e,t,a){"use strict";var r=a("14Sl"),i=a("glrk"),n=a("ewvW"),s=a("UMSQ"),o=a("ppGB"),l=a("HYAF"),d=a("iqWW"),c=a("FMNM"),u=Math.max,f=Math.min,p=Math.floor,m=/\$([$&'`]|\d\d?|<[^>]*>)/g,g=/\$([$&'`]|\d\d?)/g;r("replace",2,(function(e,t,a,r){var h=r.REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE,v=r.REPLACE_KEEPS_$0,b=h?"$":"$0";return[function(a,r){var i=l(this),n=null==a?void 0:a[e];return void 0!==n?n.call(a,i,r):t.call(String(i),a,r)},function(e,r){if(!h&&v||"string"==typeof r&&-1===r.indexOf(b)){var n=a(t,e,this,r);if(n.done)return n.value}var l=i(e),p=String(this),m="function"==typeof r;m||(r=String(r));var g=l.global;if(g){var E=l.unicode;l.lastIndex=0}for(var S=[];;){var x=c(l,p);if(null===x)break;if(S.push(x),!g)break;""===String(x[0])&&(l.lastIndex=d(p,s(l.lastIndex),E))}for(var w,L="",I=0,R=0;R=I&&(L+=p.slice(I,P)+V,I=P+N.length)}return L+p.slice(I)}];function y(e,a,r,i,s,o){var l=r+e.length,d=i.length,c=g;return void 0!==s&&(s=n(s),c=m),t.call(o,c,(function(t,n){var o;switch(n.charAt(0)){case"$":return"$";case"&":return e;case"`":return a.slice(0,r);case"'":return a.slice(l);case"<":o=s[n.slice(1,-1)];break;default:var c=+n;if(0===c)return t;if(c>d){var u=p(c/10);return 0===u?t:u<=d?void 0===i[u-1]?n.charAt(1):i[u-1]+n.charAt(1):t}o=i[c-1]}return void 0===o?"":o}))}}))},rgsX:function(e,t,a){"use strict";var r=a("q1tI"),i=a.n(r),n=a("17x9"),s=a.n(n),o=a("Wbzz"),l=a("9eSz"),d=a.n(l),c=a("6cYQ"),u=a("zpb6"),f=a("+pmV"),p=a.n(f),m=function(e){var t=e.title,a=e.date,r=e.path,n=e.coverImage,s=e.author,l=e.excerpt,f=e.tags,m=e.series,g=e.html,h=e.previousPost,v=e.nextPost,b=h&&h.frontmatter.path,y=h&&h.frontmatter.title,E=v&&v.frontmatter.path,S=v&&v.frontmatter.title;return i.a.createElement("div",{className:p.a.post},i.a.createElement("div",{className:p.a.postContent},i.a.createElement("h1",{className:p.a.title},l?i.a.createElement(o.Link,{to:r},t):t),i.a.createElement("div",{className:p.a.meta},a," ",s&&i.a.createElement(i.a.Fragment,null,"— Written by ",s),f?i.a.createElement("div",{className:p.a.tags},f.map((function(e){return i.a.createElement(o.Link,{to:"/tag/"+Object(u.toKebabCase)(e)+"/",key:Object(u.toKebabCase)(e)},i.a.createElement("span",{className:p.a.tag},"#",e))}))):null),l?i.a.createElement(i.a.Fragment,null):m&&i.a.createElement("div",{className:p.a.series},m.map((function(e,t){return e.path?i.a.createElement(o.Link,{to:e.path,key:t},e.path===r?i.a.createElement("span",{className:p.a.seriesItemBold},e.title):i.a.createElement("span",{className:p.a.seriesItem},e.title)):i.a.createElement("span",{key:t,className:p.a.seriesItem},e.title)}))),n?i.a.createElement(d.a,{fluid:n.childImageSharp.fluid,className:p.a.coverImage}):i.a.createElement("p",null,l),l?i.a.createElement(i.a.Fragment,null):i.a.createElement(i.a.Fragment,null,i.a.createElement("div",{dangerouslySetInnerHTML:{__html:g}}),i.a.createElement("script",{src:"https://utteranc.es/client.js",repo:"boorownie/blog-comment","issue-term":"pathname",theme:"github-light",crossOrigin:"anonymous",async:!0}),i.a.createElement(c.a,{previousPath:b,previousLabel:y,nextPath:E,nextLabel:S}))))};m.propTypes={title:s.a.string,date:s.a.string,path:s.a.string,coverImage:s.a.object,author:s.a.string,excerpt:s.a.string,html:s.a.string,tags:s.a.arrayOf(s.a.string),series:s.a.arrayOf(s.a.object),previousPost:s.a.object,nextPost:s.a.object},t.a=m},zHTP:function(e,t,a){e.exports={navigation:"navigation-module--navigation--3Zfju",button:"navigation-module--button--28kp3",buttonText:"navigation-module--buttonText--1Xod2",iconNext:"navigation-module--iconNext--3xyJ-",iconPrev:"navigation-module--iconPrev--23mg1"}},zpb6:function(e,t,a){a("TWNs"),a("UxlC"),e.exports.toKebabCase=function(e){return e.replace(new RegExp("(\\s|_|-)+","gmi"),"-")}}}]); +//# sourceMappingURL=23c3d3a3a2879427593569aafa02d04d4d5b9e2a-b2fb258daae83e93cfa3.js.map \ No newline at end of file diff --git a/23c3d3a3a2879427593569aafa02d04d4d5b9e2a-b2fb258daae83e93cfa3.js.map b/23c3d3a3a2879427593569aafa02d04d4d5b9e2a-b2fb258daae83e93cfa3.js.map new file mode 100644 index 0000000..73530b6 --- /dev/null +++ b/23c3d3a3a2879427593569aafa02d04d4d5b9e2a-b2fb258daae83e93cfa3.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["webpack:///./src/styles/post.module.css","webpack:///./src/components/navigation.js","webpack:///./node_modules/gatsby-image/index.js","webpack:///./node_modules/core-js/modules/es.regexp.constructor.js","webpack:///./node_modules/core-js/modules/es.string.replace.js","webpack:///./src/components/post.js","webpack:///./src/styles/navigation.module.css","webpack:///./src/helpers/index.js"],"names":["module","exports","Navigation","nextPath","previousPath","nextLabel","previousLabel","className","style","navigation","button","to","iconPrev","buttonText","iconNext","propTypes","PropTypes","string","_interopRequireDefault","require","__esModule","default","io","_assertThisInitialized2","_inheritsLoose2","_objectWithoutPropertiesLoose2","_extends2","_react","_propTypes","convertProps","props","convertedProps","resolutions","sizes","critical","fixed","fluid","loading","groupByMedia","concat","matchesMedia","_ref","media","isBrowser","window","matchMedia","matches","getImageCacheKey","_ref2","srcData","getCurrentSrcData","src","currentData","Array","isArray","some","image","hasArtDirectionSupport","foundMedia","findIndex","noMedia","imageCache","Object","create","inImageCache","cacheKey","hasNativeLazyLoadSupport","HTMLImageElement","prototype","hasIOSupport","IntersectionObserver","listeners","WeakMap","generateImageSources","imageVariants","map","_ref3","srcSet","srcSetWebp","createElement","Fragment","key","type","withMedia","without","forEach","variant","push","generateTracedSVGSources","_ref4","tracedSVG","generateBase64Sources","_ref5","base64","generateNoscriptSource","_ref6","isWebp","listenToIntersections","el","cb","observer","entries","entry","has","target","get","isIntersecting","intersectionRatio","unobserve","delete","rootMargin","observe","set","noscriptImg","title","alt","width","height","crossOrigin","draggable","join","Placeholder","forwardRef","ref","generateSources","spreadProps","ariaHidden","baseImage","Img","length","onLoad","onError","otherProps","position","top","left","objectFit","objectPosition","object","func","Image","_React$Component","_this","call","this","seenBefore","isCritical","addNoScript","fadeIn","useIOSupport","isVisible","state","imgLoaded","imgCached","isHydrated","imageRef","createRef","placeholderRef","handleImageLoaded","bind","handleRef","_proto","componentDidMount","setState","onStartLoad","wasCached","img","current","complete","componentWillUnmount","cleanUpListeners","_this2","imageInCache","currentSrc","render","_convertProps","_convertProps$style","_convertProps$imgStyl","imgStyle","_convertProps$placeho","placeholderStyle","placeholderClassName","backgroundColor","durationFadeIn","Tag","itemProp","shouldReveal","shouldFadeIn","imageStyle","opacity","transition","bgColor","delayHideStyle","transitionDelay","imagePlaceholderStyle","placeholderImageProps","overflow","maxWidth","maxHeight","JSON","stringify","paddingBottom","aspectRatio","bottom","right","dangerouslySetInnerHTML","__html","divStyle","display","Component","defaultProps","fixedObject","shape","number","isRequired","srcWebp","fluidObject","requireFixedOrFluid","originalPropTypes","propName","componentName","_PropTypes$checkPropT","Error","checkPropTypes","oneOfType","arrayOf","bool","oneOf","_default","DESCRIPTORS","global","isForced","inheritIfRequired","defineProperty","f","getOwnPropertyNames","isRegExp","getFlags","stickyHelpers","redefine","fails","setInternalState","setSpecies","MATCH","wellKnownSymbol","NativeRegExp","RegExp","RegExpPrototype","re1","re2","CORRECT_NEW","UNSUPPORTED_Y","RegExpWrapper","pattern","flags","sticky","thisIsRegExp","patternIsRegExp","flagsAreUndefined","undefined","constructor","source","indexOf","replace","result","proxy","configurable","it","keys","index","fixRegExpWellKnownSymbolLogic","anObject","toObject","toLength","toInteger","requireObjectCoercible","advanceStringIndex","regExpExec","max","Math","min","floor","SUBSTITUTION_SYMBOLS","SUBSTITUTION_SYMBOLS_NO_NAMED","REPLACE","nativeReplace","maybeCallNative","reason","REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE","REPLACE_KEEPS_$0","UNSAFE_SUBSTITUTE","searchValue","replaceValue","O","replacer","String","regexp","res","done","value","rx","S","functionalReplace","fullUnicode","unicode","lastIndex","results","accumulatedResult","nextSourcePosition","i","matched","captures","j","namedCaptures","groups","replacerArgs","replacement","apply","getSubstitution","slice","str","tailPos","m","symbols","match","ch","capture","charAt","n","Post","date","path","coverImage","author","excerpt","tags","series","html","previousPost","nextPost","frontmatter","post","postContent","meta","tag","toKebabCase","item","seriesItemBold","seriesItem","childImageSharp","repo","issue-term","theme","async"],"mappings":"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","file":"23c3d3a3a2879427593569aafa02d04d4d5b9e2a-b2fb258daae83e93cfa3.js","sourcesContent":["// extracted by mini-css-extract-plugin\nmodule.exports = {\"post\":\"post-module--post--28Mq2\",\"title\":\"post-module--title--3XBo2\",\"coverImage\":\"post-module--coverImage--1GM7V\",\"meta\":\"post-module--meta--3YtjE\",\"tags\":\"post-module--tags--3RbqF\",\"tag\":\"post-module--tag--16U9p\",\"series\":\"post-module--series--3YUjN\",\"seriesItem\":\"post-module--series-item--mOT0Y\",\"seriesItemBold\":\"post-module--series-item-bold--2Vyvw\",\"readMore\":\"post-module--readMore--3zWML\",\"postContent\":\"post-module--postContent--1bfnt\"};","import React from 'react'\nimport PropTypes from 'prop-types'\nimport { Link } from 'gatsby'\n\nimport style from '../styles/navigation.module.css'\n\nconst Navigation = ({ nextPath, previousPath, nextLabel, previousLabel }) =>\n previousPath || nextPath ? (\n
\n {previousPath && (\n \n \n \n {previousLabel}\n \n \n )}\n {nextPath && (\n \n \n {nextLabel}\n \n \n \n )}\n
\n ) : null\n\nNavigation.propTypes = {\n nextPath: PropTypes.string,\n previousPath: PropTypes.string,\n nextLabel: PropTypes.string,\n previousLabel: PropTypes.string,\n}\n\nexport default Navigation\n","\"use strict\";\n\nvar _interopRequireDefault = require(\"@babel/runtime/helpers/interopRequireDefault\");\n\nexports.__esModule = true;\nexports.default = void 0;\n\nvar _assertThisInitialized2 = _interopRequireDefault(require(\"@babel/runtime/helpers/assertThisInitialized\"));\n\nvar _inheritsLoose2 = _interopRequireDefault(require(\"@babel/runtime/helpers/inheritsLoose\"));\n\nvar _objectWithoutPropertiesLoose2 = _interopRequireDefault(require(\"@babel/runtime/helpers/objectWithoutPropertiesLoose\"));\n\nvar _extends2 = _interopRequireDefault(require(\"@babel/runtime/helpers/extends\"));\n\nvar _react = _interopRequireDefault(require(\"react\"));\n\nvar _propTypes = _interopRequireDefault(require(\"prop-types\"));\n\nvar logDeprecationNotice = function logDeprecationNotice(prop, replacement) {\n if (process.env.NODE_ENV === \"production\") {\n return;\n }\n\n console.log(\"\\n The \\\"\" + prop + \"\\\" prop is now deprecated and will be removed in the next major version\\n of \\\"gatsby-image\\\".\\n \");\n\n if (replacement) {\n console.log(\"Please use \" + replacement + \" instead of \\\"\" + prop + \"\\\".\");\n }\n}; // Handle legacy props during their deprecation phase\n\n\nvar convertProps = function convertProps(props) {\n var convertedProps = (0, _extends2.default)({}, props);\n var resolutions = convertedProps.resolutions,\n sizes = convertedProps.sizes,\n critical = convertedProps.critical;\n\n if (resolutions) {\n convertedProps.fixed = resolutions;\n logDeprecationNotice(\"resolutions\", \"the gatsby-image v2 prop \\\"fixed\\\"\");\n delete convertedProps.resolutions;\n }\n\n if (sizes) {\n convertedProps.fluid = sizes;\n logDeprecationNotice(\"sizes\", \"the gatsby-image v2 prop \\\"fluid\\\"\");\n delete convertedProps.sizes;\n }\n\n if (critical) {\n logDeprecationNotice(\"critical\", \"the native \\\"loading\\\" attribute\");\n convertedProps.loading = \"eager\";\n } // convert fluid & fixed to arrays so we only have to work with arrays\n\n\n if (convertedProps.fluid) {\n convertedProps.fluid = groupByMedia([].concat(convertedProps.fluid));\n }\n\n if (convertedProps.fixed) {\n convertedProps.fixed = groupByMedia([].concat(convertedProps.fixed));\n }\n\n return convertedProps;\n};\n/**\n * Checks if fluid or fixed are art-direction arrays.\n *\n * @param currentData {{media?: string}[]} The props to check for images.\n * @return {boolean}\n */\n\n\nvar hasArtDirectionSupport = function hasArtDirectionSupport(currentData) {\n return !!currentData && Array.isArray(currentData) && currentData.some(function (image) {\n return typeof image.media !== \"undefined\";\n });\n};\n/**\n * Tries to detect if a media query matches the current viewport.\n * @property media {{media?: string}} A media query string.\n * @return {boolean}\n */\n\n\nvar matchesMedia = function matchesMedia(_ref) {\n var media = _ref.media;\n return media ? isBrowser && !!window.matchMedia(media).matches : false;\n};\n/**\n * Find the source of an image to use as a key in the image cache.\n * Use `the first image in either `fixed` or `fluid`\n * @param {{fluid: {src: string, media?: string}[], fixed: {src: string, media?: string}[]}} args\n * @return {string?} Returns image src or undefined it not given.\n */\n\n\nvar getImageCacheKey = function getImageCacheKey(_ref2) {\n var fluid = _ref2.fluid,\n fixed = _ref2.fixed;\n var srcData = getCurrentSrcData(fluid || fixed || []);\n return srcData && srcData.src;\n};\n/**\n * Returns the current src - Preferably with art-direction support.\n * @param currentData {{media?: string}[], maxWidth?: Number, maxHeight?: Number} The fluid or fixed image array.\n * @return {{src: string, media?: string, maxWidth?: Number, maxHeight?: Number}}\n */\n\n\nvar getCurrentSrcData = function getCurrentSrcData(currentData) {\n if (isBrowser && hasArtDirectionSupport(currentData)) {\n // Do we have an image for the current Viewport?\n var foundMedia = currentData.findIndex(matchesMedia);\n\n if (foundMedia !== -1) {\n return currentData[foundMedia];\n } // No media matches, select first element without a media condition\n\n\n var noMedia = currentData.findIndex(function (image) {\n return typeof image.media === \"undefined\";\n });\n\n if (noMedia !== -1) {\n return currentData[noMedia];\n }\n } // Else return the first image.\n\n\n return currentData[0];\n}; // Cache if we've seen an image before so we don't bother with\n// lazy-loading & fading in on subsequent mounts.\n\n\nvar imageCache = Object.create({});\n\nvar inImageCache = function inImageCache(props) {\n var convertedProps = convertProps(props);\n var cacheKey = getImageCacheKey(convertedProps);\n return imageCache[cacheKey] || false;\n};\n\nvar activateCacheForImage = function activateCacheForImage(props) {\n var convertedProps = convertProps(props);\n var cacheKey = getImageCacheKey(convertedProps);\n\n if (cacheKey) {\n imageCache[cacheKey] = true;\n }\n}; // Native lazy-loading support: https://addyosmani.com/blog/lazy-loading/\n\n\nvar hasNativeLazyLoadSupport = typeof HTMLImageElement !== \"undefined\" && \"loading\" in HTMLImageElement.prototype;\nvar isBrowser = typeof window !== \"undefined\";\nvar hasIOSupport = isBrowser && window.IntersectionObserver;\nvar io;\nvar listeners = new WeakMap();\n\nfunction getIO() {\n if (typeof io === \"undefined\" && typeof window !== \"undefined\" && window.IntersectionObserver) {\n io = new window.IntersectionObserver(function (entries) {\n entries.forEach(function (entry) {\n if (listeners.has(entry.target)) {\n var cb = listeners.get(entry.target); // Edge doesn't currently support isIntersecting, so also test for an intersectionRatio > 0\n\n if (entry.isIntersecting || entry.intersectionRatio > 0) {\n io.unobserve(entry.target);\n listeners.delete(entry.target);\n cb();\n }\n }\n });\n }, {\n rootMargin: \"200px\"\n });\n }\n\n return io;\n}\n\nfunction generateImageSources(imageVariants) {\n return imageVariants.map(function (_ref3) {\n var src = _ref3.src,\n srcSet = _ref3.srcSet,\n srcSetWebp = _ref3.srcSetWebp,\n media = _ref3.media,\n sizes = _ref3.sizes;\n return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, {\n key: src\n }, srcSetWebp && /*#__PURE__*/_react.default.createElement(\"source\", {\n type: \"image/webp\",\n media: media,\n srcSet: srcSetWebp,\n sizes: sizes\n }), srcSet && /*#__PURE__*/_react.default.createElement(\"source\", {\n media: media,\n srcSet: srcSet,\n sizes: sizes\n }));\n });\n} // Return an array ordered by elements having a media prop, does not use\n// native sort, as a stable sort is not guaranteed by all browsers/versions\n\n\nfunction groupByMedia(imageVariants) {\n var withMedia = [];\n var without = [];\n imageVariants.forEach(function (variant) {\n return (variant.media ? withMedia : without).push(variant);\n });\n\n if (without.length > 1 && process.env.NODE_ENV !== \"production\") {\n console.warn(\"We've found \" + without.length + \" sources without a media property. They might be ignored by the browser, see: https://www.gatsbyjs.org/packages/gatsby-image/#art-directing-multiple-images\");\n }\n\n return [].concat(withMedia, without);\n}\n\nfunction generateTracedSVGSources(imageVariants) {\n return imageVariants.map(function (_ref4) {\n var src = _ref4.src,\n media = _ref4.media,\n tracedSVG = _ref4.tracedSVG;\n return /*#__PURE__*/_react.default.createElement(\"source\", {\n key: src,\n media: media,\n srcSet: tracedSVG\n });\n });\n}\n\nfunction generateBase64Sources(imageVariants) {\n return imageVariants.map(function (_ref5) {\n var src = _ref5.src,\n media = _ref5.media,\n base64 = _ref5.base64;\n return /*#__PURE__*/_react.default.createElement(\"source\", {\n key: src,\n media: media,\n srcSet: base64\n });\n });\n}\n\nfunction generateNoscriptSource(_ref6, isWebp) {\n var srcSet = _ref6.srcSet,\n srcSetWebp = _ref6.srcSetWebp,\n media = _ref6.media,\n sizes = _ref6.sizes;\n var src = isWebp ? srcSetWebp : srcSet;\n var mediaAttr = media ? \"media=\\\"\" + media + \"\\\" \" : \"\";\n var typeAttr = isWebp ? \"type='image/webp' \" : \"\";\n var sizesAttr = sizes ? \"sizes=\\\"\" + sizes + \"\\\" \" : \"\";\n return \"\";\n}\n\nfunction generateNoscriptSources(imageVariants) {\n return imageVariants.map(function (variant) {\n return (variant.srcSetWebp ? generateNoscriptSource(variant, true) : \"\") + generateNoscriptSource(variant);\n }).join(\"\");\n}\n\nvar listenToIntersections = function listenToIntersections(el, cb) {\n var observer = getIO();\n\n if (observer) {\n observer.observe(el);\n listeners.set(el, cb);\n }\n\n return function () {\n observer.unobserve(el);\n listeners.delete(el);\n };\n};\n\nvar noscriptImg = function noscriptImg(props) {\n // Check if prop exists before adding each attribute to the string output below to prevent\n // HTML validation issues caused by empty values like width=\"\" and height=\"\"\n var src = props.src ? \"src=\\\"\" + props.src + \"\\\" \" : \"src=\\\"\\\" \"; // required attribute\n\n var sizes = props.sizes ? \"sizes=\\\"\" + props.sizes + \"\\\" \" : \"\";\n var srcSet = props.srcSet ? \"srcset=\\\"\" + props.srcSet + \"\\\" \" : \"\";\n var title = props.title ? \"title=\\\"\" + props.title + \"\\\" \" : \"\";\n var alt = props.alt ? \"alt=\\\"\" + props.alt + \"\\\" \" : \"alt=\\\"\\\" \"; // required attribute\n\n var width = props.width ? \"width=\\\"\" + props.width + \"\\\" \" : \"\";\n var height = props.height ? \"height=\\\"\" + props.height + \"\\\" \" : \"\";\n var crossOrigin = props.crossOrigin ? \"crossorigin=\\\"\" + props.crossOrigin + \"\\\" \" : \"\";\n var loading = props.loading ? \"loading=\\\"\" + props.loading + \"\\\" \" : \"\";\n var draggable = props.draggable ? \"draggable=\\\"\" + props.draggable + \"\\\" \" : \"\";\n var sources = generateNoscriptSources(props.imageVariants);\n return \"\" + sources + \"\";\n}; // Earlier versions of gatsby-image during the 2.x cycle did not wrap\n// the `Img` component in a `picture` element. This maintains compatibility\n// until a breaking change can be introduced in the next major release\n\n\nvar Placeholder = /*#__PURE__*/_react.default.forwardRef(function (props, ref) {\n var src = props.src,\n imageVariants = props.imageVariants,\n generateSources = props.generateSources,\n spreadProps = props.spreadProps,\n ariaHidden = props.ariaHidden;\n\n var baseImage = /*#__PURE__*/_react.default.createElement(Img, (0, _extends2.default)({\n ref: ref,\n src: src\n }, spreadProps, {\n ariaHidden: ariaHidden\n }));\n\n return imageVariants.length > 1 ? /*#__PURE__*/_react.default.createElement(\"picture\", null, generateSources(imageVariants), baseImage) : baseImage;\n});\n\nvar Img = /*#__PURE__*/_react.default.forwardRef(function (props, ref) {\n var sizes = props.sizes,\n srcSet = props.srcSet,\n src = props.src,\n style = props.style,\n onLoad = props.onLoad,\n onError = props.onError,\n loading = props.loading,\n draggable = props.draggable,\n ariaHidden = props.ariaHidden,\n otherProps = (0, _objectWithoutPropertiesLoose2.default)(props, [\"sizes\", \"srcSet\", \"src\", \"style\", \"onLoad\", \"onError\", \"loading\", \"draggable\", \"ariaHidden\"]);\n return /*#__PURE__*/_react.default.createElement(\"img\", (0, _extends2.default)({\n \"aria-hidden\": ariaHidden,\n sizes: sizes,\n srcSet: srcSet,\n src: src\n }, otherProps, {\n onLoad: onLoad,\n onError: onError,\n ref: ref,\n loading: loading,\n draggable: draggable,\n style: (0, _extends2.default)({\n position: \"absolute\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n objectFit: \"cover\",\n objectPosition: \"center\"\n }, style)\n }));\n});\n\nImg.propTypes = {\n style: _propTypes.default.object,\n onError: _propTypes.default.func,\n onLoad: _propTypes.default.func\n};\n\nvar Image = /*#__PURE__*/function (_React$Component) {\n (0, _inheritsLoose2.default)(Image, _React$Component);\n\n function Image(props) {\n var _this;\n\n _this = _React$Component.call(this, props) || this; // If this image has already been loaded before then we can assume it's\n // already in the browser cache so it's cheap to just show directly.\n\n _this.seenBefore = isBrowser && inImageCache(props);\n _this.isCritical = props.loading === \"eager\" || props.critical;\n _this.addNoScript = !(_this.isCritical && !props.fadeIn);\n _this.useIOSupport = !hasNativeLazyLoadSupport && hasIOSupport && !_this.isCritical && !_this.seenBefore;\n var isVisible = _this.isCritical || isBrowser && (hasNativeLazyLoadSupport || !_this.useIOSupport);\n _this.state = {\n isVisible: isVisible,\n imgLoaded: false,\n imgCached: false,\n fadeIn: !_this.seenBefore && props.fadeIn,\n isHydrated: false\n };\n _this.imageRef = /*#__PURE__*/_react.default.createRef();\n _this.placeholderRef = props.placeholderRef || /*#__PURE__*/_react.default.createRef();\n _this.handleImageLoaded = _this.handleImageLoaded.bind((0, _assertThisInitialized2.default)(_this));\n _this.handleRef = _this.handleRef.bind((0, _assertThisInitialized2.default)(_this));\n return _this;\n }\n\n var _proto = Image.prototype;\n\n _proto.componentDidMount = function componentDidMount() {\n this.setState({\n isHydrated: isBrowser\n });\n\n if (this.state.isVisible && typeof this.props.onStartLoad === \"function\") {\n this.props.onStartLoad({\n wasCached: inImageCache(this.props)\n });\n }\n\n if (this.isCritical) {\n var img = this.imageRef.current;\n\n if (img && img.complete) {\n this.handleImageLoaded();\n }\n }\n };\n\n _proto.componentWillUnmount = function componentWillUnmount() {\n if (this.cleanUpListeners) {\n this.cleanUpListeners();\n }\n } // Specific to IntersectionObserver based lazy-load support\n ;\n\n _proto.handleRef = function handleRef(ref) {\n var _this2 = this;\n\n if (this.useIOSupport && ref) {\n this.cleanUpListeners = listenToIntersections(ref, function () {\n var imageInCache = inImageCache(_this2.props);\n\n if (!_this2.state.isVisible && typeof _this2.props.onStartLoad === \"function\") {\n _this2.props.onStartLoad({\n wasCached: imageInCache\n });\n } // imgCached and imgLoaded must update after isVisible,\n // Once isVisible is true, imageRef becomes accessible, which imgCached needs access to.\n // imgLoaded and imgCached are in a 2nd setState call to be changed together,\n // avoiding initiating unnecessary animation frames from style changes.\n\n\n _this2.setState({\n isVisible: true\n }, function () {\n _this2.setState({\n imgLoaded: imageInCache,\n // `currentSrc` should be a string, but can be `undefined` in IE,\n // !! operator validates the value is not undefined/null/\"\"\n // for lazyloaded components this might be null\n // TODO fix imgCached behaviour as it's now false when it's lazyloaded\n imgCached: !!(_this2.imageRef.current && _this2.imageRef.current.currentSrc)\n });\n });\n });\n }\n };\n\n _proto.handleImageLoaded = function handleImageLoaded() {\n activateCacheForImage(this.props);\n this.setState({\n imgLoaded: true\n });\n\n if (this.props.onLoad) {\n this.props.onLoad();\n }\n };\n\n _proto.render = function render() {\n var _convertProps = convertProps(this.props),\n title = _convertProps.title,\n alt = _convertProps.alt,\n className = _convertProps.className,\n _convertProps$style = _convertProps.style,\n style = _convertProps$style === void 0 ? {} : _convertProps$style,\n _convertProps$imgStyl = _convertProps.imgStyle,\n imgStyle = _convertProps$imgStyl === void 0 ? {} : _convertProps$imgStyl,\n _convertProps$placeho = _convertProps.placeholderStyle,\n placeholderStyle = _convertProps$placeho === void 0 ? {} : _convertProps$placeho,\n placeholderClassName = _convertProps.placeholderClassName,\n fluid = _convertProps.fluid,\n fixed = _convertProps.fixed,\n backgroundColor = _convertProps.backgroundColor,\n durationFadeIn = _convertProps.durationFadeIn,\n Tag = _convertProps.Tag,\n itemProp = _convertProps.itemProp,\n loading = _convertProps.loading,\n draggable = _convertProps.draggable;\n\n var imageVariants = fluid || fixed; // Abort early if missing image data (#25371)\n\n if (!imageVariants) {\n return null;\n }\n\n var shouldReveal = this.state.fadeIn === false || this.state.imgLoaded;\n var shouldFadeIn = this.state.fadeIn === true && !this.state.imgCached;\n var imageStyle = (0, _extends2.default)({\n opacity: shouldReveal ? 1 : 0,\n transition: shouldFadeIn ? \"opacity \" + durationFadeIn + \"ms\" : \"none\"\n }, imgStyle);\n var bgColor = typeof backgroundColor === \"boolean\" ? \"lightgray\" : backgroundColor;\n var delayHideStyle = {\n transitionDelay: durationFadeIn + \"ms\"\n };\n var imagePlaceholderStyle = (0, _extends2.default)({\n opacity: this.state.imgLoaded ? 0 : 1\n }, shouldFadeIn && delayHideStyle, imgStyle, placeholderStyle);\n var placeholderImageProps = {\n title: title,\n alt: !this.state.isVisible ? alt : \"\",\n style: imagePlaceholderStyle,\n className: placeholderClassName,\n itemProp: itemProp\n }; // Initial client render state needs to match SSR until hydration finishes.\n // Once hydration completes, render again to update to the correct image.\n // `imageVariants` is always an Array type at this point due to `convertProps()`\n\n var image = !this.state.isHydrated ? imageVariants[0] : getCurrentSrcData(imageVariants);\n\n if (fluid) {\n return /*#__PURE__*/_react.default.createElement(Tag, {\n className: (className ? className : \"\") + \" gatsby-image-wrapper\",\n style: (0, _extends2.default)({\n position: \"relative\",\n overflow: \"hidden\",\n maxWidth: image.maxWidth ? image.maxWidth + \"px\" : null,\n maxHeight: image.maxHeight ? image.maxHeight + \"px\" : null\n }, style),\n ref: this.handleRef,\n key: \"fluid-\" + JSON.stringify(image.srcSet)\n }, /*#__PURE__*/_react.default.createElement(Tag, {\n \"aria-hidden\": true,\n style: {\n width: \"100%\",\n paddingBottom: 100 / image.aspectRatio + \"%\"\n }\n }), bgColor && /*#__PURE__*/_react.default.createElement(Tag, {\n \"aria-hidden\": true,\n title: title,\n style: (0, _extends2.default)({\n backgroundColor: bgColor,\n position: \"absolute\",\n top: 0,\n bottom: 0,\n opacity: !this.state.imgLoaded ? 1 : 0,\n right: 0,\n left: 0\n }, shouldFadeIn && delayHideStyle)\n }), image.base64 && /*#__PURE__*/_react.default.createElement(Placeholder, {\n ariaHidden: true,\n ref: this.placeholderRef,\n src: image.base64,\n spreadProps: placeholderImageProps,\n imageVariants: imageVariants,\n generateSources: generateBase64Sources\n }), image.tracedSVG && /*#__PURE__*/_react.default.createElement(Placeholder, {\n ariaHidden: true,\n ref: this.placeholderRef,\n src: image.tracedSVG,\n spreadProps: placeholderImageProps,\n imageVariants: imageVariants,\n generateSources: generateTracedSVGSources\n }), this.state.isVisible && /*#__PURE__*/_react.default.createElement(\"picture\", null, generateImageSources(imageVariants), /*#__PURE__*/_react.default.createElement(Img, {\n alt: alt,\n title: title,\n sizes: image.sizes,\n src: image.src,\n crossOrigin: this.props.crossOrigin,\n srcSet: image.srcSet,\n style: imageStyle,\n ref: this.imageRef,\n onLoad: this.handleImageLoaded,\n onError: this.props.onError,\n itemProp: itemProp,\n loading: loading,\n draggable: draggable\n })), this.addNoScript && /*#__PURE__*/_react.default.createElement(\"noscript\", {\n dangerouslySetInnerHTML: {\n __html: noscriptImg((0, _extends2.default)({\n alt: alt,\n title: title,\n loading: loading\n }, image, {\n imageVariants: imageVariants\n }))\n }\n }));\n }\n\n if (fixed) {\n var divStyle = (0, _extends2.default)({\n position: \"relative\",\n overflow: \"hidden\",\n display: \"inline-block\",\n width: image.width,\n height: image.height\n }, style);\n\n if (style.display === \"inherit\") {\n delete divStyle.display;\n }\n\n return /*#__PURE__*/_react.default.createElement(Tag, {\n className: (className ? className : \"\") + \" gatsby-image-wrapper\",\n style: divStyle,\n ref: this.handleRef,\n key: \"fixed-\" + JSON.stringify(image.srcSet)\n }, bgColor && /*#__PURE__*/_react.default.createElement(Tag, {\n \"aria-hidden\": true,\n title: title,\n style: (0, _extends2.default)({\n backgroundColor: bgColor,\n width: image.width,\n opacity: !this.state.imgLoaded ? 1 : 0,\n height: image.height\n }, shouldFadeIn && delayHideStyle)\n }), image.base64 && /*#__PURE__*/_react.default.createElement(Placeholder, {\n ariaHidden: true,\n ref: this.placeholderRef,\n src: image.base64,\n spreadProps: placeholderImageProps,\n imageVariants: imageVariants,\n generateSources: generateBase64Sources\n }), image.tracedSVG && /*#__PURE__*/_react.default.createElement(Placeholder, {\n ariaHidden: true,\n ref: this.placeholderRef,\n src: image.tracedSVG,\n spreadProps: placeholderImageProps,\n imageVariants: imageVariants,\n generateSources: generateTracedSVGSources\n }), this.state.isVisible && /*#__PURE__*/_react.default.createElement(\"picture\", null, generateImageSources(imageVariants), /*#__PURE__*/_react.default.createElement(Img, {\n alt: alt,\n title: title,\n width: image.width,\n height: image.height,\n sizes: image.sizes,\n src: image.src,\n crossOrigin: this.props.crossOrigin,\n srcSet: image.srcSet,\n style: imageStyle,\n ref: this.imageRef,\n onLoad: this.handleImageLoaded,\n onError: this.props.onError,\n itemProp: itemProp,\n loading: loading,\n draggable: draggable\n })), this.addNoScript && /*#__PURE__*/_react.default.createElement(\"noscript\", {\n dangerouslySetInnerHTML: {\n __html: noscriptImg((0, _extends2.default)({\n alt: alt,\n title: title,\n loading: loading\n }, image, {\n imageVariants: imageVariants\n }))\n }\n }));\n }\n\n return null;\n };\n\n return Image;\n}(_react.default.Component);\n\nImage.defaultProps = {\n fadeIn: true,\n durationFadeIn: 500,\n alt: \"\",\n Tag: \"div\",\n // We set it to `lazy` by default because it's best to default to a performant\n // setting and let the user \"opt out\" to `eager`\n loading: \"lazy\"\n};\n\nvar fixedObject = _propTypes.default.shape({\n width: _propTypes.default.number.isRequired,\n height: _propTypes.default.number.isRequired,\n src: _propTypes.default.string.isRequired,\n srcSet: _propTypes.default.string.isRequired,\n base64: _propTypes.default.string,\n tracedSVG: _propTypes.default.string,\n srcWebp: _propTypes.default.string,\n srcSetWebp: _propTypes.default.string,\n media: _propTypes.default.string\n});\n\nvar fluidObject = _propTypes.default.shape({\n aspectRatio: _propTypes.default.number.isRequired,\n src: _propTypes.default.string.isRequired,\n srcSet: _propTypes.default.string.isRequired,\n sizes: _propTypes.default.string.isRequired,\n base64: _propTypes.default.string,\n tracedSVG: _propTypes.default.string,\n srcWebp: _propTypes.default.string,\n srcSetWebp: _propTypes.default.string,\n media: _propTypes.default.string,\n maxWidth: _propTypes.default.number,\n maxHeight: _propTypes.default.number\n});\n\nfunction requireFixedOrFluid(originalPropTypes) {\n return function (props, propName, componentName) {\n var _PropTypes$checkPropT;\n\n if (!props.fixed && !props.fluid) {\n throw new Error(\"The prop `fluid` or `fixed` is marked as required in `\" + componentName + \"`, but their values are both `undefined`.\");\n }\n\n _propTypes.default.checkPropTypes((_PropTypes$checkPropT = {}, _PropTypes$checkPropT[propName] = originalPropTypes, _PropTypes$checkPropT), props, \"prop\", componentName);\n };\n} // If you modify these propTypes, please don't forget to update following files as well:\n// https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-image/index.d.ts\n// https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-image/README.md#gatsby-image-props\n// https://github.com/gatsbyjs/gatsby/blob/master/docs/docs/gatsby-image.md#gatsby-image-props\n\n\nImage.propTypes = {\n resolutions: fixedObject,\n sizes: fluidObject,\n fixed: requireFixedOrFluid(_propTypes.default.oneOfType([fixedObject, _propTypes.default.arrayOf(fixedObject)])),\n fluid: requireFixedOrFluid(_propTypes.default.oneOfType([fluidObject, _propTypes.default.arrayOf(fluidObject)])),\n fadeIn: _propTypes.default.bool,\n durationFadeIn: _propTypes.default.number,\n title: _propTypes.default.string,\n alt: _propTypes.default.string,\n className: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.object]),\n // Support Glamor's css prop.\n critical: _propTypes.default.bool,\n crossOrigin: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.bool]),\n style: _propTypes.default.object,\n imgStyle: _propTypes.default.object,\n placeholderStyle: _propTypes.default.object,\n placeholderClassName: _propTypes.default.string,\n backgroundColor: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.bool]),\n onLoad: _propTypes.default.func,\n onError: _propTypes.default.func,\n onStartLoad: _propTypes.default.func,\n Tag: _propTypes.default.string,\n itemProp: _propTypes.default.string,\n loading: _propTypes.default.oneOf([\"auto\", \"lazy\", \"eager\"]),\n draggable: _propTypes.default.bool\n};\nvar _default = Image;\nexports.default = _default;","var DESCRIPTORS = require('../internals/descriptors');\nvar global = require('../internals/global');\nvar isForced = require('../internals/is-forced');\nvar inheritIfRequired = require('../internals/inherit-if-required');\nvar defineProperty = require('../internals/object-define-property').f;\nvar getOwnPropertyNames = require('../internals/object-get-own-property-names').f;\nvar isRegExp = require('../internals/is-regexp');\nvar getFlags = require('../internals/regexp-flags');\nvar stickyHelpers = require('../internals/regexp-sticky-helpers');\nvar redefine = require('../internals/redefine');\nvar fails = require('../internals/fails');\nvar setInternalState = require('../internals/internal-state').set;\nvar setSpecies = require('../internals/set-species');\nvar wellKnownSymbol = require('../internals/well-known-symbol');\n\nvar MATCH = wellKnownSymbol('match');\nvar NativeRegExp = global.RegExp;\nvar RegExpPrototype = NativeRegExp.prototype;\nvar re1 = /a/g;\nvar re2 = /a/g;\n\n// \"new\" should create a new object, old webkit bug\nvar CORRECT_NEW = new NativeRegExp(re1) !== re1;\n\nvar UNSUPPORTED_Y = stickyHelpers.UNSUPPORTED_Y;\n\nvar FORCED = DESCRIPTORS && isForced('RegExp', (!CORRECT_NEW || UNSUPPORTED_Y || fails(function () {\n re2[MATCH] = false;\n // RegExp constructor can alter flags and IsRegExp works correct with @@match\n return NativeRegExp(re1) != re1 || NativeRegExp(re2) == re2 || NativeRegExp(re1, 'i') != '/a/i';\n})));\n\n// `RegExp` constructor\n// https://tc39.github.io/ecma262/#sec-regexp-constructor\nif (FORCED) {\n var RegExpWrapper = function RegExp(pattern, flags) {\n var thisIsRegExp = this instanceof RegExpWrapper;\n var patternIsRegExp = isRegExp(pattern);\n var flagsAreUndefined = flags === undefined;\n var sticky;\n\n if (!thisIsRegExp && patternIsRegExp && pattern.constructor === RegExpWrapper && flagsAreUndefined) {\n return pattern;\n }\n\n if (CORRECT_NEW) {\n if (patternIsRegExp && !flagsAreUndefined) pattern = pattern.source;\n } else if (pattern instanceof RegExpWrapper) {\n if (flagsAreUndefined) flags = getFlags.call(pattern);\n pattern = pattern.source;\n }\n\n if (UNSUPPORTED_Y) {\n sticky = !!flags && flags.indexOf('y') > -1;\n if (sticky) flags = flags.replace(/y/g, '');\n }\n\n var result = inheritIfRequired(\n CORRECT_NEW ? new NativeRegExp(pattern, flags) : NativeRegExp(pattern, flags),\n thisIsRegExp ? this : RegExpPrototype,\n RegExpWrapper\n );\n\n if (UNSUPPORTED_Y && sticky) setInternalState(result, { sticky: sticky });\n\n return result;\n };\n var proxy = function (key) {\n key in RegExpWrapper || defineProperty(RegExpWrapper, key, {\n configurable: true,\n get: function () { return NativeRegExp[key]; },\n set: function (it) { NativeRegExp[key] = it; }\n });\n };\n var keys = getOwnPropertyNames(NativeRegExp);\n var index = 0;\n while (keys.length > index) proxy(keys[index++]);\n RegExpPrototype.constructor = RegExpWrapper;\n RegExpWrapper.prototype = RegExpPrototype;\n redefine(global, 'RegExp', RegExpWrapper);\n}\n\n// https://tc39.github.io/ecma262/#sec-get-regexp-@@species\nsetSpecies('RegExp');\n","'use strict';\nvar fixRegExpWellKnownSymbolLogic = require('../internals/fix-regexp-well-known-symbol-logic');\nvar anObject = require('../internals/an-object');\nvar toObject = require('../internals/to-object');\nvar toLength = require('../internals/to-length');\nvar toInteger = require('../internals/to-integer');\nvar requireObjectCoercible = require('../internals/require-object-coercible');\nvar advanceStringIndex = require('../internals/advance-string-index');\nvar regExpExec = require('../internals/regexp-exec-abstract');\n\nvar max = Math.max;\nvar min = Math.min;\nvar floor = Math.floor;\nvar SUBSTITUTION_SYMBOLS = /\\$([$&'`]|\\d\\d?|<[^>]*>)/g;\nvar SUBSTITUTION_SYMBOLS_NO_NAMED = /\\$([$&'`]|\\d\\d?)/g;\n\nvar maybeToString = function (it) {\n return it === undefined ? it : String(it);\n};\n\n// @@replace logic\nfixRegExpWellKnownSymbolLogic('replace', 2, function (REPLACE, nativeReplace, maybeCallNative, reason) {\n var REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE = reason.REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE;\n var REPLACE_KEEPS_$0 = reason.REPLACE_KEEPS_$0;\n var UNSAFE_SUBSTITUTE = REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE ? '$' : '$0';\n\n return [\n // `String.prototype.replace` method\n // https://tc39.github.io/ecma262/#sec-string.prototype.replace\n function replace(searchValue, replaceValue) {\n var O = requireObjectCoercible(this);\n var replacer = searchValue == undefined ? undefined : searchValue[REPLACE];\n return replacer !== undefined\n ? replacer.call(searchValue, O, replaceValue)\n : nativeReplace.call(String(O), searchValue, replaceValue);\n },\n // `RegExp.prototype[@@replace]` method\n // https://tc39.github.io/ecma262/#sec-regexp.prototype-@@replace\n function (regexp, replaceValue) {\n if (\n (!REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE && REPLACE_KEEPS_$0) ||\n (typeof replaceValue === 'string' && replaceValue.indexOf(UNSAFE_SUBSTITUTE) === -1)\n ) {\n var res = maybeCallNative(nativeReplace, regexp, this, replaceValue);\n if (res.done) return res.value;\n }\n\n var rx = anObject(regexp);\n var S = String(this);\n\n var functionalReplace = typeof replaceValue === 'function';\n if (!functionalReplace) replaceValue = String(replaceValue);\n\n var global = rx.global;\n if (global) {\n var fullUnicode = rx.unicode;\n rx.lastIndex = 0;\n }\n var results = [];\n while (true) {\n var result = regExpExec(rx, S);\n if (result === null) break;\n\n results.push(result);\n if (!global) break;\n\n var matchStr = String(result[0]);\n if (matchStr === '') rx.lastIndex = advanceStringIndex(S, toLength(rx.lastIndex), fullUnicode);\n }\n\n var accumulatedResult = '';\n var nextSourcePosition = 0;\n for (var i = 0; i < results.length; i++) {\n result = results[i];\n\n var matched = String(result[0]);\n var position = max(min(toInteger(result.index), S.length), 0);\n var captures = [];\n // NOTE: This is equivalent to\n // captures = result.slice(1).map(maybeToString)\n // but for some reason `nativeSlice.call(result, 1, result.length)` (called in\n // the slice polyfill when slicing native arrays) \"doesn't work\" in safari 9 and\n // causes a crash (https://pastebin.com/N21QzeQA) when trying to debug it.\n for (var j = 1; j < result.length; j++) captures.push(maybeToString(result[j]));\n var namedCaptures = result.groups;\n if (functionalReplace) {\n var replacerArgs = [matched].concat(captures, position, S);\n if (namedCaptures !== undefined) replacerArgs.push(namedCaptures);\n var replacement = String(replaceValue.apply(undefined, replacerArgs));\n } else {\n replacement = getSubstitution(matched, S, position, captures, namedCaptures, replaceValue);\n }\n if (position >= nextSourcePosition) {\n accumulatedResult += S.slice(nextSourcePosition, position) + replacement;\n nextSourcePosition = position + matched.length;\n }\n }\n return accumulatedResult + S.slice(nextSourcePosition);\n }\n ];\n\n // https://tc39.github.io/ecma262/#sec-getsubstitution\n function getSubstitution(matched, str, position, captures, namedCaptures, replacement) {\n var tailPos = position + matched.length;\n var m = captures.length;\n var symbols = SUBSTITUTION_SYMBOLS_NO_NAMED;\n if (namedCaptures !== undefined) {\n namedCaptures = toObject(namedCaptures);\n symbols = SUBSTITUTION_SYMBOLS;\n }\n return nativeReplace.call(replacement, symbols, function (match, ch) {\n var capture;\n switch (ch.charAt(0)) {\n case '$': return '$';\n case '&': return matched;\n case '`': return str.slice(0, position);\n case \"'\": return str.slice(tailPos);\n case '<':\n capture = namedCaptures[ch.slice(1, -1)];\n break;\n default: // \\d\\d?\n var n = +ch;\n if (n === 0) return match;\n if (n > m) {\n var f = floor(n / 10);\n if (f === 0) return match;\n if (f <= m) return captures[f - 1] === undefined ? ch.charAt(1) : captures[f - 1] + ch.charAt(1);\n return match;\n }\n capture = captures[n - 1];\n }\n return capture === undefined ? '' : capture;\n });\n }\n});\n","import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport { Link } from \"gatsby\";\nimport Img from \"gatsby-image\";\nimport Navigation from \"./navigation\";\nimport { toKebabCase } from \"../helpers\";\n\nimport style from \"../styles/post.module.css\";\n\nconst Post = ({\n title,\n date,\n path,\n coverImage,\n author,\n excerpt,\n tags,\n series,\n html,\n previousPost,\n nextPost,\n}) => {\n const previousPath = previousPost && previousPost.frontmatter.path;\n const previousLabel = previousPost && previousPost.frontmatter.title;\n const nextPath = nextPost && nextPost.frontmatter.path;\n const nextLabel = nextPost && nextPost.frontmatter.title;\n\n return (\n
\n
\n

\n {excerpt ? {title} : title}\n

\n
\n {date} {author && <>— Written by {author}}\n {tags ? (\n
\n {tags.map(tag => (\n \n #{tag}\n \n ))}\n
\n ) : null}\n
\n\n {excerpt ? (\n <>\n \n ) : (\n series && (\n
\n {series.map((item, index) => (\n item.path ? (\n \n {\n item.path === path ? (\n {item.title}\n ) : (\n {item.title}\n )\n }\n \n ) : (\n {item.title}\n )\n ))}\n
\n )\n )}\n\n {coverImage ? (\n \n ) : (\n

{excerpt}

\n )}\n\n {excerpt ? (\n <>\n {/*

{excerpt}

*/}\n {/**/}\n {/* Read more →*/}\n {/**/}\n \n ) : (\n <>\n
\n \n \n \n )}\n
\n
\n );\n};\n\nPost.propTypes = {\n title: PropTypes.string,\n date: PropTypes.string,\n path: PropTypes.string,\n coverImage: PropTypes.object,\n author: PropTypes.string,\n excerpt: PropTypes.string,\n html: PropTypes.string,\n tags: PropTypes.arrayOf(PropTypes.string),\n series: PropTypes.arrayOf(PropTypes.object),\n previousPost: PropTypes.object,\n nextPost: PropTypes.object,\n};\n\nexport default Post;\n","// extracted by mini-css-extract-plugin\nmodule.exports = {\"navigation\":\"navigation-module--navigation--3Zfju\",\"button\":\"navigation-module--button--28kp3\",\"buttonText\":\"navigation-module--buttonText--1Xod2\",\"iconNext\":\"navigation-module--iconNext--3xyJ-\",\"iconPrev\":\"navigation-module--iconPrev--23mg1\"};","module.exports.toKebabCase = function(value) {\n return value.replace(new RegExp('(\\\\s|_|-)+', 'gmi'), '-')\n}\n"],"sourceRoot":""} \ No newline at end of file diff --git a/23c3d3a3a2879427593569aafa02d04d4d5b9e2a-e402d624b262f62849ba.js b/23c3d3a3a2879427593569aafa02d04d4d5b9e2a-e402d624b262f62849ba.js new file mode 100644 index 0000000..3aebdbd --- /dev/null +++ b/23c3d3a3a2879427593569aafa02d04d4d5b9e2a-e402d624b262f62849ba.js @@ -0,0 +1,2 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[2],{"+pmV":function(e,t,a){e.exports={post:"post-module--post--28Mq2",title:"post-module--title--3XBo2",coverImage:"post-module--coverImage--1GM7V",meta:"post-module--meta--3YtjE",tags:"post-module--tags--3RbqF",tag:"post-module--tag--16U9p",series:"post-module--series--3YUjN",seriesItem:"post-module--series-item--mOT0Y",seriesItemBold:"post-module--series-item-bold--2Vyvw",readMore:"post-module--readMore--3zWML",postContent:"post-module--postContent--1bfnt"}},"6cYQ":function(e,t,a){"use strict";var r=a("q1tI"),i=a.n(r),n=a("17x9"),s=a.n(n),o=a("Wbzz"),l=a("zHTP"),d=a.n(l),c=function(e){var t=e.nextPath,a=e.previousPath,r=e.nextLabel,n=e.previousLabel;return a||t?i.a.createElement("div",{className:d.a.navigation},a&&i.a.createElement("span",{className:d.a.button},i.a.createElement(o.Link,{to:a},i.a.createElement("span",{className:d.a.iconPrev},"←"),i.a.createElement("span",{className:d.a.buttonText},n))),t&&i.a.createElement("span",{className:d.a.button},i.a.createElement(o.Link,{to:t},i.a.createElement("span",{className:d.a.buttonText},r),i.a.createElement("span",{className:d.a.iconNext},"→")))):null};c.propTypes={nextPath:s.a.string,previousPath:s.a.string,nextLabel:s.a.string,previousLabel:s.a.string},t.a=c},"9eSz":function(e,t,a){"use strict";var r=a("TqRt");t.__esModule=!0,t.default=void 0;var i,n=r(a("PJYZ")),s=r(a("VbXa")),o=r(a("8OQS")),l=r(a("pVnL")),d=r(a("q1tI")),c=r(a("17x9")),u=function(e){var t=(0,l.default)({},e),a=t.resolutions,r=t.sizes,i=t.critical;return a&&(t.fixed=a,delete t.resolutions),r&&(t.fluid=r,delete t.sizes),i&&(t.loading="eager"),t.fluid&&(t.fluid=x([].concat(t.fluid))),t.fixed&&(t.fixed=x([].concat(t.fixed))),t},f=function(e){var t=e.media;return!!t&&(b&&!!window.matchMedia(t).matches)},p=function(e){var t=e.fluid,a=e.fixed,r=m(t||a||[]);return r&&r.src},m=function(e){if(b&&function(e){return!!e&&Array.isArray(e)&&e.some((function(e){return void 0!==e.media}))}(e)){var t=e.findIndex(f);if(-1!==t)return e[t];var a=e.findIndex((function(e){return void 0===e.media}));if(-1!==a)return e[a]}return e[0]},g=Object.create({}),h=function(e){var t=u(e),a=p(t);return g[a]||!1},v="undefined"!=typeof HTMLImageElement&&"loading"in HTMLImageElement.prototype,b="undefined"!=typeof window,y=b&&window.IntersectionObserver,E=new WeakMap;function S(e){return e.map((function(e){var t=e.src,a=e.srcSet,r=e.srcSetWebp,i=e.media,n=e.sizes;return d.default.createElement(d.default.Fragment,{key:t},r&&d.default.createElement("source",{type:"image/webp",media:i,srcSet:r,sizes:n}),a&&d.default.createElement("source",{media:i,srcSet:a,sizes:n}))}))}function x(e){var t=[],a=[];return e.forEach((function(e){return(e.media?t:a).push(e)})),[].concat(t,a)}function w(e){return e.map((function(e){var t=e.src,a=e.media,r=e.tracedSVG;return d.default.createElement("source",{key:t,media:a,srcSet:r})}))}function L(e){return e.map((function(e){var t=e.src,a=e.media,r=e.base64;return d.default.createElement("source",{key:t,media:a,srcSet:r})}))}function I(e,t){var a=e.srcSet,r=e.srcSetWebp,i=e.media,n=e.sizes;return""}var R=function(e,t){var a=(void 0===i&&"undefined"!=typeof window&&window.IntersectionObserver&&(i=new window.IntersectionObserver((function(e){e.forEach((function(e){if(E.has(e.target)){var t=E.get(e.target);(e.isIntersecting||e.intersectionRatio>0)&&(i.unobserve(e.target),E.delete(e.target),t())}}))}),{rootMargin:"200px"})),i);return a&&(a.observe(e),E.set(e,t)),function(){a.unobserve(e),E.delete(e)}},N=function(e){var t=e.src?'src="'+e.src+'" ':'src="" ',a=e.sizes?'sizes="'+e.sizes+'" ':"",r=e.srcSet?'srcset="'+e.srcSet+'" ':"",i=e.title?'title="'+e.title+'" ':"",n=e.alt?'alt="'+e.alt+'" ':'alt="" ',s=e.width?'width="'+e.width+'" ':"",o=e.height?'height="'+e.height+'" ':"",l=e.crossOrigin?'crossorigin="'+e.crossOrigin+'" ':"",d=e.loading?'loading="'+e.loading+'" ':"",c=e.draggable?'draggable="'+e.draggable+'" ':"";return""+e.imageVariants.map((function(e){return(e.srcSetWebp?I(e,!0):"")+I(e)})).join("")+"'},P=d.default.forwardRef((function(e,t){var a=e.src,r=e.imageVariants,i=e.generateSources,n=e.spreadProps,s=e.ariaHidden,o=d.default.createElement(O,(0,l.default)({ref:t,src:a},n,{ariaHidden:s}));return r.length>1?d.default.createElement("picture",null,i(r),o):o})),O=d.default.forwardRef((function(e,t){var a=e.sizes,r=e.srcSet,i=e.src,n=e.style,s=e.onLoad,c=e.onError,u=e.loading,f=e.draggable,p=e.ariaHidden,m=(0,o.default)(e,["sizes","srcSet","src","style","onLoad","onError","loading","draggable","ariaHidden"]);return d.default.createElement("img",(0,l.default)({"aria-hidden":p,sizes:a,srcSet:r,src:i},m,{onLoad:s,onError:c,ref:t,loading:u,draggable:f,style:(0,l.default)({position:"absolute",top:0,left:0,width:"100%",height:"100%",objectFit:"cover",objectPosition:"center"},n)}))}));O.propTypes={style:c.default.object,onError:c.default.func,onLoad:c.default.func};var T=function(e){function t(t){var a;(a=e.call(this,t)||this).seenBefore=b&&h(t),a.isCritical="eager"===t.loading||t.critical,a.addNoScript=!(a.isCritical&&!t.fadeIn),a.useIOSupport=!v&&y&&!a.isCritical&&!a.seenBefore;var r=a.isCritical||b&&(v||!a.useIOSupport);return a.state={isVisible:r,imgLoaded:!1,imgCached:!1,fadeIn:!a.seenBefore&&t.fadeIn,isHydrated:!1},a.imageRef=d.default.createRef(),a.placeholderRef=t.placeholderRef||d.default.createRef(),a.handleImageLoaded=a.handleImageLoaded.bind((0,n.default)(a)),a.handleRef=a.handleRef.bind((0,n.default)(a)),a}(0,s.default)(t,e);var a=t.prototype;return a.componentDidMount=function(){if(this.setState({isHydrated:b}),this.state.isVisible&&"function"==typeof this.props.onStartLoad&&this.props.onStartLoad({wasCached:h(this.props)}),this.isCritical){var e=this.imageRef.current;e&&e.complete&&this.handleImageLoaded()}},a.componentWillUnmount=function(){this.cleanUpListeners&&this.cleanUpListeners()},a.handleRef=function(e){var t=this;this.useIOSupport&&e&&(this.cleanUpListeners=R(e,(function(){var e=h(t.props);t.state.isVisible||"function"!=typeof t.props.onStartLoad||t.props.onStartLoad({wasCached:e}),t.setState({isVisible:!0},(function(){t.setState({imgLoaded:e,imgCached:!(!t.imageRef.current||!t.imageRef.current.currentSrc)})}))})))},a.handleImageLoaded=function(){var e,t,a;e=this.props,t=u(e),(a=p(t))&&(g[a]=!0),this.setState({imgLoaded:!0}),this.props.onLoad&&this.props.onLoad()},a.render=function(){var e=u(this.props),t=e.title,a=e.alt,r=e.className,i=e.style,n=void 0===i?{}:i,s=e.imgStyle,o=void 0===s?{}:s,c=e.placeholderStyle,f=void 0===c?{}:c,p=e.placeholderClassName,g=e.fluid,h=e.fixed,v=e.backgroundColor,b=e.durationFadeIn,y=e.Tag,E=e.itemProp,x=e.loading,I=e.draggable,R=g||h;if(!R)return null;var T=!1===this.state.fadeIn||this.state.imgLoaded,z=!0===this.state.fadeIn&&!this.state.imgCached,k=(0,l.default)({opacity:T?1:0,transition:z?"opacity "+b+"ms":"none"},o),V="boolean"==typeof v?"lightgray":v,C={transitionDelay:b+"ms"},H=(0,l.default)({opacity:this.state.imgLoaded?0:1},z&&C,o,f),W={title:t,alt:this.state.isVisible?"":a,style:H,className:p,itemProp:E},M=this.state.isHydrated?m(R):R[0];if(g)return d.default.createElement(y,{className:(r||"")+" gatsby-image-wrapper",style:(0,l.default)({position:"relative",overflow:"hidden",maxWidth:M.maxWidth?M.maxWidth+"px":null,maxHeight:M.maxHeight?M.maxHeight+"px":null},n),ref:this.handleRef,key:"fluid-"+JSON.stringify(M.srcSet)},d.default.createElement(y,{"aria-hidden":!0,style:{width:"100%",paddingBottom:100/M.aspectRatio+"%"}}),V&&d.default.createElement(y,{"aria-hidden":!0,title:t,style:(0,l.default)({backgroundColor:V,position:"absolute",top:0,bottom:0,opacity:this.state.imgLoaded?0:1,right:0,left:0},z&&C)}),M.base64&&d.default.createElement(P,{ariaHidden:!0,ref:this.placeholderRef,src:M.base64,spreadProps:W,imageVariants:R,generateSources:L}),M.tracedSVG&&d.default.createElement(P,{ariaHidden:!0,ref:this.placeholderRef,src:M.tracedSVG,spreadProps:W,imageVariants:R,generateSources:w}),this.state.isVisible&&d.default.createElement("picture",null,S(R),d.default.createElement(O,{alt:a,title:t,sizes:M.sizes,src:M.src,crossOrigin:this.props.crossOrigin,srcSet:M.srcSet,style:k,ref:this.imageRef,onLoad:this.handleImageLoaded,onError:this.props.onError,itemProp:E,loading:x,draggable:I})),this.addNoScript&&d.default.createElement("noscript",{dangerouslySetInnerHTML:{__html:N((0,l.default)({alt:a,title:t,loading:x},M,{imageVariants:R}))}}));if(h){var j=(0,l.default)({position:"relative",overflow:"hidden",display:"inline-block",width:M.width,height:M.height},n);return"inherit"===n.display&&delete j.display,d.default.createElement(y,{className:(r||"")+" gatsby-image-wrapper",style:j,ref:this.handleRef,key:"fixed-"+JSON.stringify(M.srcSet)},V&&d.default.createElement(y,{"aria-hidden":!0,title:t,style:(0,l.default)({backgroundColor:V,width:M.width,opacity:this.state.imgLoaded?0:1,height:M.height},z&&C)}),M.base64&&d.default.createElement(P,{ariaHidden:!0,ref:this.placeholderRef,src:M.base64,spreadProps:W,imageVariants:R,generateSources:L}),M.tracedSVG&&d.default.createElement(P,{ariaHidden:!0,ref:this.placeholderRef,src:M.tracedSVG,spreadProps:W,imageVariants:R,generateSources:w}),this.state.isVisible&&d.default.createElement("picture",null,S(R),d.default.createElement(O,{alt:a,title:t,width:M.width,height:M.height,sizes:M.sizes,src:M.src,crossOrigin:this.props.crossOrigin,srcSet:M.srcSet,style:k,ref:this.imageRef,onLoad:this.handleImageLoaded,onError:this.props.onError,itemProp:E,loading:x,draggable:I})),this.addNoScript&&d.default.createElement("noscript",{dangerouslySetInnerHTML:{__html:N((0,l.default)({alt:a,title:t,loading:x},M,{imageVariants:R}))}}))}return null},t}(d.default.Component);T.defaultProps={fadeIn:!0,durationFadeIn:500,alt:"",Tag:"div",loading:"lazy"};var z=c.default.shape({width:c.default.number.isRequired,height:c.default.number.isRequired,src:c.default.string.isRequired,srcSet:c.default.string.isRequired,base64:c.default.string,tracedSVG:c.default.string,srcWebp:c.default.string,srcSetWebp:c.default.string,media:c.default.string}),k=c.default.shape({aspectRatio:c.default.number.isRequired,src:c.default.string.isRequired,srcSet:c.default.string.isRequired,sizes:c.default.string.isRequired,base64:c.default.string,tracedSVG:c.default.string,srcWebp:c.default.string,srcSetWebp:c.default.string,media:c.default.string,maxWidth:c.default.number,maxHeight:c.default.number});function V(e){return function(t,a,r){var i;if(!t.fixed&&!t.fluid)throw new Error("The prop `fluid` or `fixed` is marked as required in `"+r+"`, but their values are both `undefined`.");c.default.checkPropTypes(((i={})[a]=e,i),t,"prop",r)}}T.propTypes={resolutions:z,sizes:k,fixed:V(c.default.oneOfType([z,c.default.arrayOf(z)])),fluid:V(c.default.oneOfType([k,c.default.arrayOf(k)])),fadeIn:c.default.bool,durationFadeIn:c.default.number,title:c.default.string,alt:c.default.string,className:c.default.oneOfType([c.default.string,c.default.object]),critical:c.default.bool,crossOrigin:c.default.oneOfType([c.default.string,c.default.bool]),style:c.default.object,imgStyle:c.default.object,placeholderStyle:c.default.object,placeholderClassName:c.default.string,backgroundColor:c.default.oneOfType([c.default.string,c.default.bool]),onLoad:c.default.func,onError:c.default.func,onStartLoad:c.default.func,Tag:c.default.string,itemProp:c.default.string,loading:c.default.oneOf(["auto","lazy","eager"]),draggable:c.default.bool};var C=T;t.default=C},TWNs:function(e,t,a){var r=a("g6v/"),i=a("2oRo"),n=a("lMq5"),s=a("cVYH"),o=a("m/L8").f,l=a("JBy8").f,d=a("ROdP"),c=a("rW0t"),u=a("n3/R"),f=a("busE"),p=a("0Dky"),m=a("afO8").set,g=a("JiZb"),h=a("tiKp")("match"),v=i.RegExp,b=v.prototype,y=/a/g,E=/a/g,S=new v(y)!==y,x=u.UNSUPPORTED_Y;if(r&&n("RegExp",!S||x||p((function(){return E[h]=!1,v(y)!=y||v(E)==E||"/a/i"!=v(y,"i")})))){for(var w=function(e,t){var a,r=this instanceof w,i=d(e),n=void 0===t;if(!r&&i&&e.constructor===w&&n)return e;S?i&&!n&&(e=e.source):e instanceof w&&(n&&(t=c.call(e)),e=e.source),x&&(a=!!t&&t.indexOf("y")>-1)&&(t=t.replace(/y/g,""));var o=s(S?new v(e,t):v(e,t),r?this:b,w);return x&&a&&m(o,{sticky:a}),o},L=function(e){e in w||o(w,e,{configurable:!0,get:function(){return v[e]},set:function(t){v[e]=t}})},I=l(v),R=0;I.length>R;)L(I[R++]);b.constructor=w,w.prototype=b,f(i,"RegExp",w)}g("RegExp")},UxlC:function(e,t,a){"use strict";var r=a("14Sl"),i=a("glrk"),n=a("ewvW"),s=a("UMSQ"),o=a("ppGB"),l=a("HYAF"),d=a("iqWW"),c=a("FMNM"),u=Math.max,f=Math.min,p=Math.floor,m=/\$([$&'`]|\d\d?|<[^>]*>)/g,g=/\$([$&'`]|\d\d?)/g;r("replace",2,(function(e,t,a,r){var h=r.REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE,v=r.REPLACE_KEEPS_$0,b=h?"$":"$0";return[function(a,r){var i=l(this),n=null==a?void 0:a[e];return void 0!==n?n.call(a,i,r):t.call(String(i),a,r)},function(e,r){if(!h&&v||"string"==typeof r&&-1===r.indexOf(b)){var n=a(t,e,this,r);if(n.done)return n.value}var l=i(e),p=String(this),m="function"==typeof r;m||(r=String(r));var g=l.global;if(g){var E=l.unicode;l.lastIndex=0}for(var S=[];;){var x=c(l,p);if(null===x)break;if(S.push(x),!g)break;""===String(x[0])&&(l.lastIndex=d(p,s(l.lastIndex),E))}for(var w,L="",I=0,R=0;R=I&&(L+=p.slice(I,P)+V,I=P+N.length)}return L+p.slice(I)}];function y(e,a,r,i,s,o){var l=r+e.length,d=i.length,c=g;return void 0!==s&&(s=n(s),c=m),t.call(o,c,(function(t,n){var o;switch(n.charAt(0)){case"$":return"$";case"&":return e;case"`":return a.slice(0,r);case"'":return a.slice(l);case"<":o=s[n.slice(1,-1)];break;default:var c=+n;if(0===c)return t;if(c>d){var u=p(c/10);return 0===u?t:u<=d?void 0===i[u-1]?n.charAt(1):i[u-1]+n.charAt(1):t}o=i[c-1]}return void 0===o?"":o}))}}))},rgsX:function(e,t,a){"use strict";var r=a("q1tI"),i=a.n(r),n=a("17x9"),s=a.n(n),o=a("Wbzz"),l=a("9eSz"),d=a.n(l),c=a("6cYQ"),u=a("zpb6"),f=a("+pmV"),p=a.n(f),m=function(e){var t=e.title,a=e.date,r=e.path,n=e.coverImage,s=e.author,l=e.excerpt,f=e.tags,m=e.series,g=e.html,h=e.previousPost,v=e.nextPost,b=h&&h.frontmatter.path,y=h&&h.frontmatter.title,E=v&&v.frontmatter.path,S=v&&v.frontmatter.title;return i.a.createElement("div",{className:p.a.post},i.a.createElement("div",{className:p.a.postContent},i.a.createElement("h1",{className:p.a.title},l?i.a.createElement(o.Link,{to:r},t):t),i.a.createElement("div",{className:p.a.meta},a," ",s&&i.a.createElement(i.a.Fragment,null,"— Written by ",s),f?i.a.createElement("div",{className:p.a.tags},f.map((function(e){return i.a.createElement(o.Link,{to:"/tag/"+Object(u.toKebabCase)(e)+"/",key:Object(u.toKebabCase)(e)},i.a.createElement("span",{className:p.a.tag},"#",e))}))):null),l?i.a.createElement(i.a.Fragment,null):m&&i.a.createElement("div",{className:p.a.series},m.map((function(e,t){return e.path?i.a.createElement(o.Link,{to:e.path,key:t},e.path===window.location.pathname?i.a.createElement("span",{className:p.a.seriesItemBold},e.title):i.a.createElement("span",{className:p.a.seriesItem},e.title)):i.a.createElement("span",{key:t,className:p.a.seriesItem},e.title)}))),n?i.a.createElement(d.a,{fluid:n.childImageSharp.fluid,className:p.a.coverImage}):i.a.createElement("p",null,l),l?i.a.createElement(i.a.Fragment,null):i.a.createElement(i.a.Fragment,null,i.a.createElement("div",{dangerouslySetInnerHTML:{__html:g}}),i.a.createElement("script",{src:"https://utteranc.es/client.js",repo:"boorownie/blog-comment","issue-term":"pathname",theme:"github-light",crossOrigin:"anonymous",async:!0}),i.a.createElement(c.a,{previousPath:b,previousLabel:y,nextPath:E,nextLabel:S}))))};m.propTypes={title:s.a.string,date:s.a.string,path:s.a.string,coverImage:s.a.object,author:s.a.string,excerpt:s.a.string,html:s.a.string,tags:s.a.arrayOf(s.a.string),series:s.a.arrayOf(s.a.object),previousPost:s.a.object,nextPost:s.a.object},t.a=m},zHTP:function(e,t,a){e.exports={navigation:"navigation-module--navigation--3Zfju",button:"navigation-module--button--28kp3",buttonText:"navigation-module--buttonText--1Xod2",iconNext:"navigation-module--iconNext--3xyJ-",iconPrev:"navigation-module--iconPrev--23mg1"}},zpb6:function(e,t,a){a("TWNs"),a("UxlC"),e.exports.toKebabCase=function(e){return e.replace(new RegExp("(\\s|_|-)+","gmi"),"-")}}}]); +//# sourceMappingURL=23c3d3a3a2879427593569aafa02d04d4d5b9e2a-e402d624b262f62849ba.js.map \ No newline at end of file diff --git a/23c3d3a3a2879427593569aafa02d04d4d5b9e2a-e402d624b262f62849ba.js.map b/23c3d3a3a2879427593569aafa02d04d4d5b9e2a-e402d624b262f62849ba.js.map new file mode 100644 index 0000000..b8a74c2 --- /dev/null +++ b/23c3d3a3a2879427593569aafa02d04d4d5b9e2a-e402d624b262f62849ba.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["webpack:///./src/styles/post.module.css","webpack:///./src/components/navigation.js","webpack:///./node_modules/gatsby-image/index.js","webpack:///./node_modules/core-js/modules/es.regexp.constructor.js","webpack:///./node_modules/core-js/modules/es.string.replace.js","webpack:///./src/components/post.js","webpack:///./src/styles/navigation.module.css","webpack:///./src/helpers/index.js"],"names":["module","exports","Navigation","nextPath","previousPath","nextLabel","previousLabel","className","style","navigation","button","to","iconPrev","buttonText","iconNext","propTypes","PropTypes","string","_interopRequireDefault","require","__esModule","default","io","_assertThisInitialized2","_inheritsLoose2","_objectWithoutPropertiesLoose2","_extends2","_react","_propTypes","convertProps","props","convertedProps","resolutions","sizes","critical","fixed","fluid","loading","groupByMedia","concat","matchesMedia","_ref","media","isBrowser","window","matchMedia","matches","getImageCacheKey","_ref2","srcData","getCurrentSrcData","src","currentData","Array","isArray","some","image","hasArtDirectionSupport","foundMedia","findIndex","noMedia","imageCache","Object","create","inImageCache","cacheKey","hasNativeLazyLoadSupport","HTMLImageElement","prototype","hasIOSupport","IntersectionObserver","listeners","WeakMap","generateImageSources","imageVariants","map","_ref3","srcSet","srcSetWebp","createElement","Fragment","key","type","withMedia","without","forEach","variant","push","generateTracedSVGSources","_ref4","tracedSVG","generateBase64Sources","_ref5","base64","generateNoscriptSource","_ref6","isWebp","listenToIntersections","el","cb","observer","entries","entry","has","target","get","isIntersecting","intersectionRatio","unobserve","delete","rootMargin","observe","set","noscriptImg","title","alt","width","height","crossOrigin","draggable","join","Placeholder","forwardRef","ref","generateSources","spreadProps","ariaHidden","baseImage","Img","length","onLoad","onError","otherProps","position","top","left","objectFit","objectPosition","object","func","Image","_React$Component","_this","call","this","seenBefore","isCritical","addNoScript","fadeIn","useIOSupport","isVisible","state","imgLoaded","imgCached","isHydrated","imageRef","createRef","placeholderRef","handleImageLoaded","bind","handleRef","_proto","componentDidMount","setState","onStartLoad","wasCached","img","current","complete","componentWillUnmount","cleanUpListeners","_this2","imageInCache","currentSrc","render","_convertProps","_convertProps$style","_convertProps$imgStyl","imgStyle","_convertProps$placeho","placeholderStyle","placeholderClassName","backgroundColor","durationFadeIn","Tag","itemProp","shouldReveal","shouldFadeIn","imageStyle","opacity","transition","bgColor","delayHideStyle","transitionDelay","imagePlaceholderStyle","placeholderImageProps","overflow","maxWidth","maxHeight","JSON","stringify","paddingBottom","aspectRatio","bottom","right","dangerouslySetInnerHTML","__html","divStyle","display","Component","defaultProps","fixedObject","shape","number","isRequired","srcWebp","fluidObject","requireFixedOrFluid","originalPropTypes","propName","componentName","_PropTypes$checkPropT","Error","checkPropTypes","oneOfType","arrayOf","bool","oneOf","_default","DESCRIPTORS","global","isForced","inheritIfRequired","defineProperty","f","getOwnPropertyNames","isRegExp","getFlags","stickyHelpers","redefine","fails","setInternalState","setSpecies","MATCH","wellKnownSymbol","NativeRegExp","RegExp","RegExpPrototype","re1","re2","CORRECT_NEW","UNSUPPORTED_Y","RegExpWrapper","pattern","flags","sticky","thisIsRegExp","patternIsRegExp","flagsAreUndefined","undefined","constructor","source","indexOf","replace","result","proxy","configurable","it","keys","index","fixRegExpWellKnownSymbolLogic","anObject","toObject","toLength","toInteger","requireObjectCoercible","advanceStringIndex","regExpExec","max","Math","min","floor","SUBSTITUTION_SYMBOLS","SUBSTITUTION_SYMBOLS_NO_NAMED","REPLACE","nativeReplace","maybeCallNative","reason","REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE","REPLACE_KEEPS_$0","UNSAFE_SUBSTITUTE","searchValue","replaceValue","O","replacer","String","regexp","res","done","value","rx","S","functionalReplace","fullUnicode","unicode","lastIndex","results","accumulatedResult","nextSourcePosition","i","matched","captures","j","namedCaptures","groups","replacerArgs","replacement","apply","getSubstitution","slice","str","tailPos","m","symbols","match","ch","capture","charAt","n","Post","date","path","coverImage","author","excerpt","tags","series","html","previousPost","nextPost","frontmatter","post","postContent","meta","tag","toKebabCase","item","location","pathname","seriesItemBold","seriesItem","childImageSharp","repo","issue-term","theme","async"],"mappings":"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","file":"23c3d3a3a2879427593569aafa02d04d4d5b9e2a-e402d624b262f62849ba.js","sourcesContent":["// extracted by mini-css-extract-plugin\nmodule.exports = {\"post\":\"post-module--post--28Mq2\",\"title\":\"post-module--title--3XBo2\",\"coverImage\":\"post-module--coverImage--1GM7V\",\"meta\":\"post-module--meta--3YtjE\",\"tags\":\"post-module--tags--3RbqF\",\"tag\":\"post-module--tag--16U9p\",\"series\":\"post-module--series--3YUjN\",\"seriesItem\":\"post-module--series-item--mOT0Y\",\"seriesItemBold\":\"post-module--series-item-bold--2Vyvw\",\"readMore\":\"post-module--readMore--3zWML\",\"postContent\":\"post-module--postContent--1bfnt\"};","import React from 'react'\nimport PropTypes from 'prop-types'\nimport { Link } from 'gatsby'\n\nimport style from '../styles/navigation.module.css'\n\nconst Navigation = ({ nextPath, previousPath, nextLabel, previousLabel }) =>\n previousPath || nextPath ? (\n
\n {previousPath && (\n \n \n \n {previousLabel}\n \n \n )}\n {nextPath && (\n \n \n {nextLabel}\n \n \n \n )}\n
\n ) : null\n\nNavigation.propTypes = {\n nextPath: PropTypes.string,\n previousPath: PropTypes.string,\n nextLabel: PropTypes.string,\n previousLabel: PropTypes.string,\n}\n\nexport default Navigation\n","\"use strict\";\n\nvar _interopRequireDefault = require(\"@babel/runtime/helpers/interopRequireDefault\");\n\nexports.__esModule = true;\nexports.default = void 0;\n\nvar _assertThisInitialized2 = _interopRequireDefault(require(\"@babel/runtime/helpers/assertThisInitialized\"));\n\nvar _inheritsLoose2 = _interopRequireDefault(require(\"@babel/runtime/helpers/inheritsLoose\"));\n\nvar _objectWithoutPropertiesLoose2 = _interopRequireDefault(require(\"@babel/runtime/helpers/objectWithoutPropertiesLoose\"));\n\nvar _extends2 = _interopRequireDefault(require(\"@babel/runtime/helpers/extends\"));\n\nvar _react = _interopRequireDefault(require(\"react\"));\n\nvar _propTypes = _interopRequireDefault(require(\"prop-types\"));\n\nvar logDeprecationNotice = function logDeprecationNotice(prop, replacement) {\n if (process.env.NODE_ENV === \"production\") {\n return;\n }\n\n console.log(\"\\n The \\\"\" + prop + \"\\\" prop is now deprecated and will be removed in the next major version\\n of \\\"gatsby-image\\\".\\n \");\n\n if (replacement) {\n console.log(\"Please use \" + replacement + \" instead of \\\"\" + prop + \"\\\".\");\n }\n}; // Handle legacy props during their deprecation phase\n\n\nvar convertProps = function convertProps(props) {\n var convertedProps = (0, _extends2.default)({}, props);\n var resolutions = convertedProps.resolutions,\n sizes = convertedProps.sizes,\n critical = convertedProps.critical;\n\n if (resolutions) {\n convertedProps.fixed = resolutions;\n logDeprecationNotice(\"resolutions\", \"the gatsby-image v2 prop \\\"fixed\\\"\");\n delete convertedProps.resolutions;\n }\n\n if (sizes) {\n convertedProps.fluid = sizes;\n logDeprecationNotice(\"sizes\", \"the gatsby-image v2 prop \\\"fluid\\\"\");\n delete convertedProps.sizes;\n }\n\n if (critical) {\n logDeprecationNotice(\"critical\", \"the native \\\"loading\\\" attribute\");\n convertedProps.loading = \"eager\";\n } // convert fluid & fixed to arrays so we only have to work with arrays\n\n\n if (convertedProps.fluid) {\n convertedProps.fluid = groupByMedia([].concat(convertedProps.fluid));\n }\n\n if (convertedProps.fixed) {\n convertedProps.fixed = groupByMedia([].concat(convertedProps.fixed));\n }\n\n return convertedProps;\n};\n/**\n * Checks if fluid or fixed are art-direction arrays.\n *\n * @param currentData {{media?: string}[]} The props to check for images.\n * @return {boolean}\n */\n\n\nvar hasArtDirectionSupport = function hasArtDirectionSupport(currentData) {\n return !!currentData && Array.isArray(currentData) && currentData.some(function (image) {\n return typeof image.media !== \"undefined\";\n });\n};\n/**\n * Tries to detect if a media query matches the current viewport.\n * @property media {{media?: string}} A media query string.\n * @return {boolean}\n */\n\n\nvar matchesMedia = function matchesMedia(_ref) {\n var media = _ref.media;\n return media ? isBrowser && !!window.matchMedia(media).matches : false;\n};\n/**\n * Find the source of an image to use as a key in the image cache.\n * Use `the first image in either `fixed` or `fluid`\n * @param {{fluid: {src: string, media?: string}[], fixed: {src: string, media?: string}[]}} args\n * @return {string?} Returns image src or undefined it not given.\n */\n\n\nvar getImageCacheKey = function getImageCacheKey(_ref2) {\n var fluid = _ref2.fluid,\n fixed = _ref2.fixed;\n var srcData = getCurrentSrcData(fluid || fixed || []);\n return srcData && srcData.src;\n};\n/**\n * Returns the current src - Preferably with art-direction support.\n * @param currentData {{media?: string}[], maxWidth?: Number, maxHeight?: Number} The fluid or fixed image array.\n * @return {{src: string, media?: string, maxWidth?: Number, maxHeight?: Number}}\n */\n\n\nvar getCurrentSrcData = function getCurrentSrcData(currentData) {\n if (isBrowser && hasArtDirectionSupport(currentData)) {\n // Do we have an image for the current Viewport?\n var foundMedia = currentData.findIndex(matchesMedia);\n\n if (foundMedia !== -1) {\n return currentData[foundMedia];\n } // No media matches, select first element without a media condition\n\n\n var noMedia = currentData.findIndex(function (image) {\n return typeof image.media === \"undefined\";\n });\n\n if (noMedia !== -1) {\n return currentData[noMedia];\n }\n } // Else return the first image.\n\n\n return currentData[0];\n}; // Cache if we've seen an image before so we don't bother with\n// lazy-loading & fading in on subsequent mounts.\n\n\nvar imageCache = Object.create({});\n\nvar inImageCache = function inImageCache(props) {\n var convertedProps = convertProps(props);\n var cacheKey = getImageCacheKey(convertedProps);\n return imageCache[cacheKey] || false;\n};\n\nvar activateCacheForImage = function activateCacheForImage(props) {\n var convertedProps = convertProps(props);\n var cacheKey = getImageCacheKey(convertedProps);\n\n if (cacheKey) {\n imageCache[cacheKey] = true;\n }\n}; // Native lazy-loading support: https://addyosmani.com/blog/lazy-loading/\n\n\nvar hasNativeLazyLoadSupport = typeof HTMLImageElement !== \"undefined\" && \"loading\" in HTMLImageElement.prototype;\nvar isBrowser = typeof window !== \"undefined\";\nvar hasIOSupport = isBrowser && window.IntersectionObserver;\nvar io;\nvar listeners = new WeakMap();\n\nfunction getIO() {\n if (typeof io === \"undefined\" && typeof window !== \"undefined\" && window.IntersectionObserver) {\n io = new window.IntersectionObserver(function (entries) {\n entries.forEach(function (entry) {\n if (listeners.has(entry.target)) {\n var cb = listeners.get(entry.target); // Edge doesn't currently support isIntersecting, so also test for an intersectionRatio > 0\n\n if (entry.isIntersecting || entry.intersectionRatio > 0) {\n io.unobserve(entry.target);\n listeners.delete(entry.target);\n cb();\n }\n }\n });\n }, {\n rootMargin: \"200px\"\n });\n }\n\n return io;\n}\n\nfunction generateImageSources(imageVariants) {\n return imageVariants.map(function (_ref3) {\n var src = _ref3.src,\n srcSet = _ref3.srcSet,\n srcSetWebp = _ref3.srcSetWebp,\n media = _ref3.media,\n sizes = _ref3.sizes;\n return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, {\n key: src\n }, srcSetWebp && /*#__PURE__*/_react.default.createElement(\"source\", {\n type: \"image/webp\",\n media: media,\n srcSet: srcSetWebp,\n sizes: sizes\n }), srcSet && /*#__PURE__*/_react.default.createElement(\"source\", {\n media: media,\n srcSet: srcSet,\n sizes: sizes\n }));\n });\n} // Return an array ordered by elements having a media prop, does not use\n// native sort, as a stable sort is not guaranteed by all browsers/versions\n\n\nfunction groupByMedia(imageVariants) {\n var withMedia = [];\n var without = [];\n imageVariants.forEach(function (variant) {\n return (variant.media ? withMedia : without).push(variant);\n });\n\n if (without.length > 1 && process.env.NODE_ENV !== \"production\") {\n console.warn(\"We've found \" + without.length + \" sources without a media property. They might be ignored by the browser, see: https://www.gatsbyjs.org/packages/gatsby-image/#art-directing-multiple-images\");\n }\n\n return [].concat(withMedia, without);\n}\n\nfunction generateTracedSVGSources(imageVariants) {\n return imageVariants.map(function (_ref4) {\n var src = _ref4.src,\n media = _ref4.media,\n tracedSVG = _ref4.tracedSVG;\n return /*#__PURE__*/_react.default.createElement(\"source\", {\n key: src,\n media: media,\n srcSet: tracedSVG\n });\n });\n}\n\nfunction generateBase64Sources(imageVariants) {\n return imageVariants.map(function (_ref5) {\n var src = _ref5.src,\n media = _ref5.media,\n base64 = _ref5.base64;\n return /*#__PURE__*/_react.default.createElement(\"source\", {\n key: src,\n media: media,\n srcSet: base64\n });\n });\n}\n\nfunction generateNoscriptSource(_ref6, isWebp) {\n var srcSet = _ref6.srcSet,\n srcSetWebp = _ref6.srcSetWebp,\n media = _ref6.media,\n sizes = _ref6.sizes;\n var src = isWebp ? srcSetWebp : srcSet;\n var mediaAttr = media ? \"media=\\\"\" + media + \"\\\" \" : \"\";\n var typeAttr = isWebp ? \"type='image/webp' \" : \"\";\n var sizesAttr = sizes ? \"sizes=\\\"\" + sizes + \"\\\" \" : \"\";\n return \"\";\n}\n\nfunction generateNoscriptSources(imageVariants) {\n return imageVariants.map(function (variant) {\n return (variant.srcSetWebp ? generateNoscriptSource(variant, true) : \"\") + generateNoscriptSource(variant);\n }).join(\"\");\n}\n\nvar listenToIntersections = function listenToIntersections(el, cb) {\n var observer = getIO();\n\n if (observer) {\n observer.observe(el);\n listeners.set(el, cb);\n }\n\n return function () {\n observer.unobserve(el);\n listeners.delete(el);\n };\n};\n\nvar noscriptImg = function noscriptImg(props) {\n // Check if prop exists before adding each attribute to the string output below to prevent\n // HTML validation issues caused by empty values like width=\"\" and height=\"\"\n var src = props.src ? \"src=\\\"\" + props.src + \"\\\" \" : \"src=\\\"\\\" \"; // required attribute\n\n var sizes = props.sizes ? \"sizes=\\\"\" + props.sizes + \"\\\" \" : \"\";\n var srcSet = props.srcSet ? \"srcset=\\\"\" + props.srcSet + \"\\\" \" : \"\";\n var title = props.title ? \"title=\\\"\" + props.title + \"\\\" \" : \"\";\n var alt = props.alt ? \"alt=\\\"\" + props.alt + \"\\\" \" : \"alt=\\\"\\\" \"; // required attribute\n\n var width = props.width ? \"width=\\\"\" + props.width + \"\\\" \" : \"\";\n var height = props.height ? \"height=\\\"\" + props.height + \"\\\" \" : \"\";\n var crossOrigin = props.crossOrigin ? \"crossorigin=\\\"\" + props.crossOrigin + \"\\\" \" : \"\";\n var loading = props.loading ? \"loading=\\\"\" + props.loading + \"\\\" \" : \"\";\n var draggable = props.draggable ? \"draggable=\\\"\" + props.draggable + \"\\\" \" : \"\";\n var sources = generateNoscriptSources(props.imageVariants);\n return \"\" + sources + \"\";\n}; // Earlier versions of gatsby-image during the 2.x cycle did not wrap\n// the `Img` component in a `picture` element. This maintains compatibility\n// until a breaking change can be introduced in the next major release\n\n\nvar Placeholder = /*#__PURE__*/_react.default.forwardRef(function (props, ref) {\n var src = props.src,\n imageVariants = props.imageVariants,\n generateSources = props.generateSources,\n spreadProps = props.spreadProps,\n ariaHidden = props.ariaHidden;\n\n var baseImage = /*#__PURE__*/_react.default.createElement(Img, (0, _extends2.default)({\n ref: ref,\n src: src\n }, spreadProps, {\n ariaHidden: ariaHidden\n }));\n\n return imageVariants.length > 1 ? /*#__PURE__*/_react.default.createElement(\"picture\", null, generateSources(imageVariants), baseImage) : baseImage;\n});\n\nvar Img = /*#__PURE__*/_react.default.forwardRef(function (props, ref) {\n var sizes = props.sizes,\n srcSet = props.srcSet,\n src = props.src,\n style = props.style,\n onLoad = props.onLoad,\n onError = props.onError,\n loading = props.loading,\n draggable = props.draggable,\n ariaHidden = props.ariaHidden,\n otherProps = (0, _objectWithoutPropertiesLoose2.default)(props, [\"sizes\", \"srcSet\", \"src\", \"style\", \"onLoad\", \"onError\", \"loading\", \"draggable\", \"ariaHidden\"]);\n return /*#__PURE__*/_react.default.createElement(\"img\", (0, _extends2.default)({\n \"aria-hidden\": ariaHidden,\n sizes: sizes,\n srcSet: srcSet,\n src: src\n }, otherProps, {\n onLoad: onLoad,\n onError: onError,\n ref: ref,\n loading: loading,\n draggable: draggable,\n style: (0, _extends2.default)({\n position: \"absolute\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n objectFit: \"cover\",\n objectPosition: \"center\"\n }, style)\n }));\n});\n\nImg.propTypes = {\n style: _propTypes.default.object,\n onError: _propTypes.default.func,\n onLoad: _propTypes.default.func\n};\n\nvar Image = /*#__PURE__*/function (_React$Component) {\n (0, _inheritsLoose2.default)(Image, _React$Component);\n\n function Image(props) {\n var _this;\n\n _this = _React$Component.call(this, props) || this; // If this image has already been loaded before then we can assume it's\n // already in the browser cache so it's cheap to just show directly.\n\n _this.seenBefore = isBrowser && inImageCache(props);\n _this.isCritical = props.loading === \"eager\" || props.critical;\n _this.addNoScript = !(_this.isCritical && !props.fadeIn);\n _this.useIOSupport = !hasNativeLazyLoadSupport && hasIOSupport && !_this.isCritical && !_this.seenBefore;\n var isVisible = _this.isCritical || isBrowser && (hasNativeLazyLoadSupport || !_this.useIOSupport);\n _this.state = {\n isVisible: isVisible,\n imgLoaded: false,\n imgCached: false,\n fadeIn: !_this.seenBefore && props.fadeIn,\n isHydrated: false\n };\n _this.imageRef = /*#__PURE__*/_react.default.createRef();\n _this.placeholderRef = props.placeholderRef || /*#__PURE__*/_react.default.createRef();\n _this.handleImageLoaded = _this.handleImageLoaded.bind((0, _assertThisInitialized2.default)(_this));\n _this.handleRef = _this.handleRef.bind((0, _assertThisInitialized2.default)(_this));\n return _this;\n }\n\n var _proto = Image.prototype;\n\n _proto.componentDidMount = function componentDidMount() {\n this.setState({\n isHydrated: isBrowser\n });\n\n if (this.state.isVisible && typeof this.props.onStartLoad === \"function\") {\n this.props.onStartLoad({\n wasCached: inImageCache(this.props)\n });\n }\n\n if (this.isCritical) {\n var img = this.imageRef.current;\n\n if (img && img.complete) {\n this.handleImageLoaded();\n }\n }\n };\n\n _proto.componentWillUnmount = function componentWillUnmount() {\n if (this.cleanUpListeners) {\n this.cleanUpListeners();\n }\n } // Specific to IntersectionObserver based lazy-load support\n ;\n\n _proto.handleRef = function handleRef(ref) {\n var _this2 = this;\n\n if (this.useIOSupport && ref) {\n this.cleanUpListeners = listenToIntersections(ref, function () {\n var imageInCache = inImageCache(_this2.props);\n\n if (!_this2.state.isVisible && typeof _this2.props.onStartLoad === \"function\") {\n _this2.props.onStartLoad({\n wasCached: imageInCache\n });\n } // imgCached and imgLoaded must update after isVisible,\n // Once isVisible is true, imageRef becomes accessible, which imgCached needs access to.\n // imgLoaded and imgCached are in a 2nd setState call to be changed together,\n // avoiding initiating unnecessary animation frames from style changes.\n\n\n _this2.setState({\n isVisible: true\n }, function () {\n _this2.setState({\n imgLoaded: imageInCache,\n // `currentSrc` should be a string, but can be `undefined` in IE,\n // !! operator validates the value is not undefined/null/\"\"\n // for lazyloaded components this might be null\n // TODO fix imgCached behaviour as it's now false when it's lazyloaded\n imgCached: !!(_this2.imageRef.current && _this2.imageRef.current.currentSrc)\n });\n });\n });\n }\n };\n\n _proto.handleImageLoaded = function handleImageLoaded() {\n activateCacheForImage(this.props);\n this.setState({\n imgLoaded: true\n });\n\n if (this.props.onLoad) {\n this.props.onLoad();\n }\n };\n\n _proto.render = function render() {\n var _convertProps = convertProps(this.props),\n title = _convertProps.title,\n alt = _convertProps.alt,\n className = _convertProps.className,\n _convertProps$style = _convertProps.style,\n style = _convertProps$style === void 0 ? {} : _convertProps$style,\n _convertProps$imgStyl = _convertProps.imgStyle,\n imgStyle = _convertProps$imgStyl === void 0 ? {} : _convertProps$imgStyl,\n _convertProps$placeho = _convertProps.placeholderStyle,\n placeholderStyle = _convertProps$placeho === void 0 ? {} : _convertProps$placeho,\n placeholderClassName = _convertProps.placeholderClassName,\n fluid = _convertProps.fluid,\n fixed = _convertProps.fixed,\n backgroundColor = _convertProps.backgroundColor,\n durationFadeIn = _convertProps.durationFadeIn,\n Tag = _convertProps.Tag,\n itemProp = _convertProps.itemProp,\n loading = _convertProps.loading,\n draggable = _convertProps.draggable;\n\n var imageVariants = fluid || fixed; // Abort early if missing image data (#25371)\n\n if (!imageVariants) {\n return null;\n }\n\n var shouldReveal = this.state.fadeIn === false || this.state.imgLoaded;\n var shouldFadeIn = this.state.fadeIn === true && !this.state.imgCached;\n var imageStyle = (0, _extends2.default)({\n opacity: shouldReveal ? 1 : 0,\n transition: shouldFadeIn ? \"opacity \" + durationFadeIn + \"ms\" : \"none\"\n }, imgStyle);\n var bgColor = typeof backgroundColor === \"boolean\" ? \"lightgray\" : backgroundColor;\n var delayHideStyle = {\n transitionDelay: durationFadeIn + \"ms\"\n };\n var imagePlaceholderStyle = (0, _extends2.default)({\n opacity: this.state.imgLoaded ? 0 : 1\n }, shouldFadeIn && delayHideStyle, imgStyle, placeholderStyle);\n var placeholderImageProps = {\n title: title,\n alt: !this.state.isVisible ? alt : \"\",\n style: imagePlaceholderStyle,\n className: placeholderClassName,\n itemProp: itemProp\n }; // Initial client render state needs to match SSR until hydration finishes.\n // Once hydration completes, render again to update to the correct image.\n // `imageVariants` is always an Array type at this point due to `convertProps()`\n\n var image = !this.state.isHydrated ? imageVariants[0] : getCurrentSrcData(imageVariants);\n\n if (fluid) {\n return /*#__PURE__*/_react.default.createElement(Tag, {\n className: (className ? className : \"\") + \" gatsby-image-wrapper\",\n style: (0, _extends2.default)({\n position: \"relative\",\n overflow: \"hidden\",\n maxWidth: image.maxWidth ? image.maxWidth + \"px\" : null,\n maxHeight: image.maxHeight ? image.maxHeight + \"px\" : null\n }, style),\n ref: this.handleRef,\n key: \"fluid-\" + JSON.stringify(image.srcSet)\n }, /*#__PURE__*/_react.default.createElement(Tag, {\n \"aria-hidden\": true,\n style: {\n width: \"100%\",\n paddingBottom: 100 / image.aspectRatio + \"%\"\n }\n }), bgColor && /*#__PURE__*/_react.default.createElement(Tag, {\n \"aria-hidden\": true,\n title: title,\n style: (0, _extends2.default)({\n backgroundColor: bgColor,\n position: \"absolute\",\n top: 0,\n bottom: 0,\n opacity: !this.state.imgLoaded ? 1 : 0,\n right: 0,\n left: 0\n }, shouldFadeIn && delayHideStyle)\n }), image.base64 && /*#__PURE__*/_react.default.createElement(Placeholder, {\n ariaHidden: true,\n ref: this.placeholderRef,\n src: image.base64,\n spreadProps: placeholderImageProps,\n imageVariants: imageVariants,\n generateSources: generateBase64Sources\n }), image.tracedSVG && /*#__PURE__*/_react.default.createElement(Placeholder, {\n ariaHidden: true,\n ref: this.placeholderRef,\n src: image.tracedSVG,\n spreadProps: placeholderImageProps,\n imageVariants: imageVariants,\n generateSources: generateTracedSVGSources\n }), this.state.isVisible && /*#__PURE__*/_react.default.createElement(\"picture\", null, generateImageSources(imageVariants), /*#__PURE__*/_react.default.createElement(Img, {\n alt: alt,\n title: title,\n sizes: image.sizes,\n src: image.src,\n crossOrigin: this.props.crossOrigin,\n srcSet: image.srcSet,\n style: imageStyle,\n ref: this.imageRef,\n onLoad: this.handleImageLoaded,\n onError: this.props.onError,\n itemProp: itemProp,\n loading: loading,\n draggable: draggable\n })), this.addNoScript && /*#__PURE__*/_react.default.createElement(\"noscript\", {\n dangerouslySetInnerHTML: {\n __html: noscriptImg((0, _extends2.default)({\n alt: alt,\n title: title,\n loading: loading\n }, image, {\n imageVariants: imageVariants\n }))\n }\n }));\n }\n\n if (fixed) {\n var divStyle = (0, _extends2.default)({\n position: \"relative\",\n overflow: \"hidden\",\n display: \"inline-block\",\n width: image.width,\n height: image.height\n }, style);\n\n if (style.display === \"inherit\") {\n delete divStyle.display;\n }\n\n return /*#__PURE__*/_react.default.createElement(Tag, {\n className: (className ? className : \"\") + \" gatsby-image-wrapper\",\n style: divStyle,\n ref: this.handleRef,\n key: \"fixed-\" + JSON.stringify(image.srcSet)\n }, bgColor && /*#__PURE__*/_react.default.createElement(Tag, {\n \"aria-hidden\": true,\n title: title,\n style: (0, _extends2.default)({\n backgroundColor: bgColor,\n width: image.width,\n opacity: !this.state.imgLoaded ? 1 : 0,\n height: image.height\n }, shouldFadeIn && delayHideStyle)\n }), image.base64 && /*#__PURE__*/_react.default.createElement(Placeholder, {\n ariaHidden: true,\n ref: this.placeholderRef,\n src: image.base64,\n spreadProps: placeholderImageProps,\n imageVariants: imageVariants,\n generateSources: generateBase64Sources\n }), image.tracedSVG && /*#__PURE__*/_react.default.createElement(Placeholder, {\n ariaHidden: true,\n ref: this.placeholderRef,\n src: image.tracedSVG,\n spreadProps: placeholderImageProps,\n imageVariants: imageVariants,\n generateSources: generateTracedSVGSources\n }), this.state.isVisible && /*#__PURE__*/_react.default.createElement(\"picture\", null, generateImageSources(imageVariants), /*#__PURE__*/_react.default.createElement(Img, {\n alt: alt,\n title: title,\n width: image.width,\n height: image.height,\n sizes: image.sizes,\n src: image.src,\n crossOrigin: this.props.crossOrigin,\n srcSet: image.srcSet,\n style: imageStyle,\n ref: this.imageRef,\n onLoad: this.handleImageLoaded,\n onError: this.props.onError,\n itemProp: itemProp,\n loading: loading,\n draggable: draggable\n })), this.addNoScript && /*#__PURE__*/_react.default.createElement(\"noscript\", {\n dangerouslySetInnerHTML: {\n __html: noscriptImg((0, _extends2.default)({\n alt: alt,\n title: title,\n loading: loading\n }, image, {\n imageVariants: imageVariants\n }))\n }\n }));\n }\n\n return null;\n };\n\n return Image;\n}(_react.default.Component);\n\nImage.defaultProps = {\n fadeIn: true,\n durationFadeIn: 500,\n alt: \"\",\n Tag: \"div\",\n // We set it to `lazy` by default because it's best to default to a performant\n // setting and let the user \"opt out\" to `eager`\n loading: \"lazy\"\n};\n\nvar fixedObject = _propTypes.default.shape({\n width: _propTypes.default.number.isRequired,\n height: _propTypes.default.number.isRequired,\n src: _propTypes.default.string.isRequired,\n srcSet: _propTypes.default.string.isRequired,\n base64: _propTypes.default.string,\n tracedSVG: _propTypes.default.string,\n srcWebp: _propTypes.default.string,\n srcSetWebp: _propTypes.default.string,\n media: _propTypes.default.string\n});\n\nvar fluidObject = _propTypes.default.shape({\n aspectRatio: _propTypes.default.number.isRequired,\n src: _propTypes.default.string.isRequired,\n srcSet: _propTypes.default.string.isRequired,\n sizes: _propTypes.default.string.isRequired,\n base64: _propTypes.default.string,\n tracedSVG: _propTypes.default.string,\n srcWebp: _propTypes.default.string,\n srcSetWebp: _propTypes.default.string,\n media: _propTypes.default.string,\n maxWidth: _propTypes.default.number,\n maxHeight: _propTypes.default.number\n});\n\nfunction requireFixedOrFluid(originalPropTypes) {\n return function (props, propName, componentName) {\n var _PropTypes$checkPropT;\n\n if (!props.fixed && !props.fluid) {\n throw new Error(\"The prop `fluid` or `fixed` is marked as required in `\" + componentName + \"`, but their values are both `undefined`.\");\n }\n\n _propTypes.default.checkPropTypes((_PropTypes$checkPropT = {}, _PropTypes$checkPropT[propName] = originalPropTypes, _PropTypes$checkPropT), props, \"prop\", componentName);\n };\n} // If you modify these propTypes, please don't forget to update following files as well:\n// https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-image/index.d.ts\n// https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-image/README.md#gatsby-image-props\n// https://github.com/gatsbyjs/gatsby/blob/master/docs/docs/gatsby-image.md#gatsby-image-props\n\n\nImage.propTypes = {\n resolutions: fixedObject,\n sizes: fluidObject,\n fixed: requireFixedOrFluid(_propTypes.default.oneOfType([fixedObject, _propTypes.default.arrayOf(fixedObject)])),\n fluid: requireFixedOrFluid(_propTypes.default.oneOfType([fluidObject, _propTypes.default.arrayOf(fluidObject)])),\n fadeIn: _propTypes.default.bool,\n durationFadeIn: _propTypes.default.number,\n title: _propTypes.default.string,\n alt: _propTypes.default.string,\n className: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.object]),\n // Support Glamor's css prop.\n critical: _propTypes.default.bool,\n crossOrigin: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.bool]),\n style: _propTypes.default.object,\n imgStyle: _propTypes.default.object,\n placeholderStyle: _propTypes.default.object,\n placeholderClassName: _propTypes.default.string,\n backgroundColor: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.bool]),\n onLoad: _propTypes.default.func,\n onError: _propTypes.default.func,\n onStartLoad: _propTypes.default.func,\n Tag: _propTypes.default.string,\n itemProp: _propTypes.default.string,\n loading: _propTypes.default.oneOf([\"auto\", \"lazy\", \"eager\"]),\n draggable: _propTypes.default.bool\n};\nvar _default = Image;\nexports.default = _default;","var DESCRIPTORS = require('../internals/descriptors');\nvar global = require('../internals/global');\nvar isForced = require('../internals/is-forced');\nvar inheritIfRequired = require('../internals/inherit-if-required');\nvar defineProperty = require('../internals/object-define-property').f;\nvar getOwnPropertyNames = require('../internals/object-get-own-property-names').f;\nvar isRegExp = require('../internals/is-regexp');\nvar getFlags = require('../internals/regexp-flags');\nvar stickyHelpers = require('../internals/regexp-sticky-helpers');\nvar redefine = require('../internals/redefine');\nvar fails = require('../internals/fails');\nvar setInternalState = require('../internals/internal-state').set;\nvar setSpecies = require('../internals/set-species');\nvar wellKnownSymbol = require('../internals/well-known-symbol');\n\nvar MATCH = wellKnownSymbol('match');\nvar NativeRegExp = global.RegExp;\nvar RegExpPrototype = NativeRegExp.prototype;\nvar re1 = /a/g;\nvar re2 = /a/g;\n\n// \"new\" should create a new object, old webkit bug\nvar CORRECT_NEW = new NativeRegExp(re1) !== re1;\n\nvar UNSUPPORTED_Y = stickyHelpers.UNSUPPORTED_Y;\n\nvar FORCED = DESCRIPTORS && isForced('RegExp', (!CORRECT_NEW || UNSUPPORTED_Y || fails(function () {\n re2[MATCH] = false;\n // RegExp constructor can alter flags and IsRegExp works correct with @@match\n return NativeRegExp(re1) != re1 || NativeRegExp(re2) == re2 || NativeRegExp(re1, 'i') != '/a/i';\n})));\n\n// `RegExp` constructor\n// https://tc39.github.io/ecma262/#sec-regexp-constructor\nif (FORCED) {\n var RegExpWrapper = function RegExp(pattern, flags) {\n var thisIsRegExp = this instanceof RegExpWrapper;\n var patternIsRegExp = isRegExp(pattern);\n var flagsAreUndefined = flags === undefined;\n var sticky;\n\n if (!thisIsRegExp && patternIsRegExp && pattern.constructor === RegExpWrapper && flagsAreUndefined) {\n return pattern;\n }\n\n if (CORRECT_NEW) {\n if (patternIsRegExp && !flagsAreUndefined) pattern = pattern.source;\n } else if (pattern instanceof RegExpWrapper) {\n if (flagsAreUndefined) flags = getFlags.call(pattern);\n pattern = pattern.source;\n }\n\n if (UNSUPPORTED_Y) {\n sticky = !!flags && flags.indexOf('y') > -1;\n if (sticky) flags = flags.replace(/y/g, '');\n }\n\n var result = inheritIfRequired(\n CORRECT_NEW ? new NativeRegExp(pattern, flags) : NativeRegExp(pattern, flags),\n thisIsRegExp ? this : RegExpPrototype,\n RegExpWrapper\n );\n\n if (UNSUPPORTED_Y && sticky) setInternalState(result, { sticky: sticky });\n\n return result;\n };\n var proxy = function (key) {\n key in RegExpWrapper || defineProperty(RegExpWrapper, key, {\n configurable: true,\n get: function () { return NativeRegExp[key]; },\n set: function (it) { NativeRegExp[key] = it; }\n });\n };\n var keys = getOwnPropertyNames(NativeRegExp);\n var index = 0;\n while (keys.length > index) proxy(keys[index++]);\n RegExpPrototype.constructor = RegExpWrapper;\n RegExpWrapper.prototype = RegExpPrototype;\n redefine(global, 'RegExp', RegExpWrapper);\n}\n\n// https://tc39.github.io/ecma262/#sec-get-regexp-@@species\nsetSpecies('RegExp');\n","'use strict';\nvar fixRegExpWellKnownSymbolLogic = require('../internals/fix-regexp-well-known-symbol-logic');\nvar anObject = require('../internals/an-object');\nvar toObject = require('../internals/to-object');\nvar toLength = require('../internals/to-length');\nvar toInteger = require('../internals/to-integer');\nvar requireObjectCoercible = require('../internals/require-object-coercible');\nvar advanceStringIndex = require('../internals/advance-string-index');\nvar regExpExec = require('../internals/regexp-exec-abstract');\n\nvar max = Math.max;\nvar min = Math.min;\nvar floor = Math.floor;\nvar SUBSTITUTION_SYMBOLS = /\\$([$&'`]|\\d\\d?|<[^>]*>)/g;\nvar SUBSTITUTION_SYMBOLS_NO_NAMED = /\\$([$&'`]|\\d\\d?)/g;\n\nvar maybeToString = function (it) {\n return it === undefined ? it : String(it);\n};\n\n// @@replace logic\nfixRegExpWellKnownSymbolLogic('replace', 2, function (REPLACE, nativeReplace, maybeCallNative, reason) {\n var REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE = reason.REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE;\n var REPLACE_KEEPS_$0 = reason.REPLACE_KEEPS_$0;\n var UNSAFE_SUBSTITUTE = REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE ? '$' : '$0';\n\n return [\n // `String.prototype.replace` method\n // https://tc39.github.io/ecma262/#sec-string.prototype.replace\n function replace(searchValue, replaceValue) {\n var O = requireObjectCoercible(this);\n var replacer = searchValue == undefined ? undefined : searchValue[REPLACE];\n return replacer !== undefined\n ? replacer.call(searchValue, O, replaceValue)\n : nativeReplace.call(String(O), searchValue, replaceValue);\n },\n // `RegExp.prototype[@@replace]` method\n // https://tc39.github.io/ecma262/#sec-regexp.prototype-@@replace\n function (regexp, replaceValue) {\n if (\n (!REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE && REPLACE_KEEPS_$0) ||\n (typeof replaceValue === 'string' && replaceValue.indexOf(UNSAFE_SUBSTITUTE) === -1)\n ) {\n var res = maybeCallNative(nativeReplace, regexp, this, replaceValue);\n if (res.done) return res.value;\n }\n\n var rx = anObject(regexp);\n var S = String(this);\n\n var functionalReplace = typeof replaceValue === 'function';\n if (!functionalReplace) replaceValue = String(replaceValue);\n\n var global = rx.global;\n if (global) {\n var fullUnicode = rx.unicode;\n rx.lastIndex = 0;\n }\n var results = [];\n while (true) {\n var result = regExpExec(rx, S);\n if (result === null) break;\n\n results.push(result);\n if (!global) break;\n\n var matchStr = String(result[0]);\n if (matchStr === '') rx.lastIndex = advanceStringIndex(S, toLength(rx.lastIndex), fullUnicode);\n }\n\n var accumulatedResult = '';\n var nextSourcePosition = 0;\n for (var i = 0; i < results.length; i++) {\n result = results[i];\n\n var matched = String(result[0]);\n var position = max(min(toInteger(result.index), S.length), 0);\n var captures = [];\n // NOTE: This is equivalent to\n // captures = result.slice(1).map(maybeToString)\n // but for some reason `nativeSlice.call(result, 1, result.length)` (called in\n // the slice polyfill when slicing native arrays) \"doesn't work\" in safari 9 and\n // causes a crash (https://pastebin.com/N21QzeQA) when trying to debug it.\n for (var j = 1; j < result.length; j++) captures.push(maybeToString(result[j]));\n var namedCaptures = result.groups;\n if (functionalReplace) {\n var replacerArgs = [matched].concat(captures, position, S);\n if (namedCaptures !== undefined) replacerArgs.push(namedCaptures);\n var replacement = String(replaceValue.apply(undefined, replacerArgs));\n } else {\n replacement = getSubstitution(matched, S, position, captures, namedCaptures, replaceValue);\n }\n if (position >= nextSourcePosition) {\n accumulatedResult += S.slice(nextSourcePosition, position) + replacement;\n nextSourcePosition = position + matched.length;\n }\n }\n return accumulatedResult + S.slice(nextSourcePosition);\n }\n ];\n\n // https://tc39.github.io/ecma262/#sec-getsubstitution\n function getSubstitution(matched, str, position, captures, namedCaptures, replacement) {\n var tailPos = position + matched.length;\n var m = captures.length;\n var symbols = SUBSTITUTION_SYMBOLS_NO_NAMED;\n if (namedCaptures !== undefined) {\n namedCaptures = toObject(namedCaptures);\n symbols = SUBSTITUTION_SYMBOLS;\n }\n return nativeReplace.call(replacement, symbols, function (match, ch) {\n var capture;\n switch (ch.charAt(0)) {\n case '$': return '$';\n case '&': return matched;\n case '`': return str.slice(0, position);\n case \"'\": return str.slice(tailPos);\n case '<':\n capture = namedCaptures[ch.slice(1, -1)];\n break;\n default: // \\d\\d?\n var n = +ch;\n if (n === 0) return match;\n if (n > m) {\n var f = floor(n / 10);\n if (f === 0) return match;\n if (f <= m) return captures[f - 1] === undefined ? ch.charAt(1) : captures[f - 1] + ch.charAt(1);\n return match;\n }\n capture = captures[n - 1];\n }\n return capture === undefined ? '' : capture;\n });\n }\n});\n","import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport { Link } from \"gatsby\";\nimport Img from \"gatsby-image\";\nimport Navigation from \"./navigation\";\nimport { toKebabCase } from \"../helpers\";\n\nimport style from \"../styles/post.module.css\";\n\nconst Post = ({\n title,\n date,\n path,\n coverImage,\n author,\n excerpt,\n tags,\n series,\n html,\n previousPost,\n nextPost,\n}) => {\n const previousPath = previousPost && previousPost.frontmatter.path;\n const previousLabel = previousPost && previousPost.frontmatter.title;\n const nextPath = nextPost && nextPost.frontmatter.path;\n const nextLabel = nextPost && nextPost.frontmatter.title;\n\n return (\n
\n
\n

\n {excerpt ? {title} : title}\n

\n
\n {date} {author && <>— Written by {author}}\n {tags ? (\n
\n {tags.map(tag => (\n \n #{tag}\n \n ))}\n
\n ) : null}\n
\n\n {excerpt ? (\n <>\n \n ) : (\n series && (\n
\n {series.map((item, index) => (\n item.path ? (\n \n {\n item.path === window.location.pathname ? (\n {item.title}\n ) : (\n {item.title}\n )\n }\n \n ) : (\n {item.title}\n )\n ))}\n
\n )\n )}\n\n {coverImage ? (\n \n ) : (\n

{excerpt}

\n )}\n\n {excerpt ? (\n <>\n {/*

{excerpt}

*/}\n {/**/}\n {/* Read more →*/}\n {/**/}\n \n ) : (\n <>\n
\n \n \n \n )}\n
\n
\n );\n};\n\nPost.propTypes = {\n title: PropTypes.string,\n date: PropTypes.string,\n path: PropTypes.string,\n coverImage: PropTypes.object,\n author: PropTypes.string,\n excerpt: PropTypes.string,\n html: PropTypes.string,\n tags: PropTypes.arrayOf(PropTypes.string),\n series: PropTypes.arrayOf(PropTypes.object),\n previousPost: PropTypes.object,\n nextPost: PropTypes.object,\n};\n\nexport default Post;\n","// extracted by mini-css-extract-plugin\nmodule.exports = {\"navigation\":\"navigation-module--navigation--3Zfju\",\"button\":\"navigation-module--button--28kp3\",\"buttonText\":\"navigation-module--buttonText--1Xod2\",\"iconNext\":\"navigation-module--iconNext--3xyJ-\",\"iconPrev\":\"navigation-module--iconPrev--23mg1\"};","module.exports.toKebabCase = function(value) {\n return value.replace(new RegExp('(\\\\s|_|-)+', 'gmi'), '-')\n}\n"],"sourceRoot":""} \ No newline at end of file diff --git a/3/index.html b/3/index.html new file mode 100644 index 0000000..e6bf29b --- /dev/null +++ b/3/index.html @@ -0,0 +1,135 @@ +가볍게 시작하는 인수 테스트 주도 개발 :: 맨땅에 코딩

가볍게 시작하는 인수 테스트 주도 개발

20 November 2020 — Written by Boorownie
#ATDD#TDD#BDD
1. 가볍게 시작하는 인수 테스트 주도 개발2. 인수 테스트 주도로 개발해보기 with Spring3. 인수 테스트 주도 개발 팁

이번 시리즈에서는 인수 테스트 주도 개발을 개발 관점으로 접근하여 용어 정리를 하고 예시를 통해 적용하는 방법을 알아보겠습니다.

+

TDD

+

많은 분들이 아시다시피 TDD 사이클은 3단계로 이루어져있습니다. + + + + image_tdd + +

+
    +
  • 실패하는 테스트 작성하기
  • +
  • 테스트를 성공시키기
  • +
  • 리팩터링 하기
  • +
+

TDD 사이클에서 크게 두 가지 중요한 점은 테스트 코드 우선과 리팩터링입니다. +하나는 요구사항을 검증하는 테스트 코드를 먼저 만들어 구현하고자 하는 것을 조금 더 명확히 하는 것이고, 다른 하나는 구현 후 리팩터링을 진행하여 다음 사이클을 대비하고 유지 보수하기 좋은 코드로 유지하게끔 하는 작업을 뒤로 미루지 않게 도와줍니다. +리팩터링에 대해서는 추후 포스팅에서 다루기로 하고 이번에는 테스트 우선에 대해서 조금 더 깊게 이야기해보겠습니다.

+

ATDD - 인수 테스트 주도 개발

+

ATDD는 시나리오 레벨의 구현하고자 하는 대상을 인수 테스트로 정하고 개발을 시작합니다. +대상을 테스트로 정하고 개발에 들어가는 TDD 사이클과 유사하다고 할 수 있습니다. +여기서 인수 테스트는 작은 단위의 모듈이 아닌 기능 시나리오 레벨의 검증을 목적으로 합니다.

+

+ + + atdd + +

+

인수 테스트 정의가 끝나면, 이 인수 테스트를 성공하게 하기 위해 작은 단위의 TDD 사이클을 반복합니다. +인수 테스트가 성공할 때까지 여러 번의 TDD 사이클이 반복되어 수행되고, 인수 테스트가 성공하면 리팩터링 단계를 거친 후 ATDD 사이클이 끝납니다.

+

TDD와 ATDD 비교

+

엄밀히 따지면 ATDD는 TDD의 여러 종류 중 하나라고 할 수 있습니다. TDD의 T(Test)를 AT(Acceptance Test)로 한정하여 진행합니다. +그러면 굳이 왜 인수 테스트라고 한정 지을까요? +앞서 이야기한 대로 테스트를 인수 테스트로 한정 지으면 테스트로 검증하고자 하는 대상이 작은 단위가 아니라 시나리오 레벨로 정할 수 있습니다.

+

시나리오 레벨의 검증

+

기존 TDD는 작은 단위에 대한 검증에는 뛰어나지만, 각각을 구현한 후 전체 영역이 잘 동작하는지 확인할 때 의도와 다른 경우가 종종 있습니다. +이때 전체 영역을 커버할 수 있는 인수 테스트를 먼저 작성하고 이 인수 테스트를 성공시키기 위해 TDD 사이클을 반복한다면 뚜렷한 방향성을 가지고 개발을 진행할 수 있습니다.

+

자연스럽게 다음 TDD 사이클로 이어지기

+

TDD를 이용하여 코드를 작성해 나갈 때 TDD 사이클을 유지하는 게 생각보다 어렵습니다. +테스트 작성 후 기능 구현하고 리팩터링을 한 뒤 다음을 어떤 걸 구현해야 할지 막막했던 경험이 생각이 나네요. +생각해보면 TDD는 단순한 규칙을 가지고 있습니다. 그래서 그런지 작은 단위의 기능을 구현하다 보면 내가 뭐를 구현하기 위해서 이 기능을 만들고 있지? 라는 생각이 들 때도 있습니다. +인수 테스트를 먼저 작성하여 전체적인 기능과 시나리오에 대한 이해를 명시적으로 선언한 뒤 개발을 진행한다면, 이후 한참 개발을 진행하다가도 이 인수 테스트를 통해 다시 한번 작업의 방향성을 확인할 수 있습니다.

+

인수 테스트, 너무 번거로운 거 아닌가?

+

인수 테스트(Acceptance Test)는 사용자 인수 테스트(User Acceptance Test)로 많이 불립니다. +사용자 인수 테스트는 말 그대로 요구사항을 사용자가 직접 검증하여 개발이 완료되었음을 증명하는 테스트입니다. +사용자가 직접 검증하기 위해서는 내부적인 코드를 활용할 수 없고 실제 서비스를 사용하는 방법과 유사하게 테스트가 진행되어야 합니다. +구체적인 기술(개발 방법)을 몰라도 요구사항 베이스에서 진행할 수 있어야 하죠. +예를 들면 브라우저나 디바이스를 통해 사용자의 Interaction(UI)부터 시작하여 전체 사이클을 검증할 수 있어야 합니다. +즉, UI부터 전체 기능이 정상적으로 동작하는지 확인할 수 있어야 합니다.

+

사용자 인수 테스트를 자동화하려면 (엄청나게) 큰 노력과 리소스가 필요할 수 있습니다. +그 테스트의 효과는 좋을 수 있겠지만, 테스트를 구성하고 유지보수를 하려면 그만큼 TDD의 허들이 높아집니다.

+

테스트 시나리오의 품질 관리

+

구체적인 행위를 검증하기보다는 비즈니스 규칙을 검증해야 테스트 시나리오의 품질을 관리하는 데 유리합니다. +상대적으로 비즈니스 규칙보다는 구체적인 행위의 변경이 빈번하게 일어나기 때문이죠.

+
+

+
+

백엔드 개발 시 앞서 이야기한 방법처럼 UI 기반 인수 테스트를 작성하기 위해서는 구체적인 행위를 검증할 수 있어야 합니다. +따라서 UI를 포함한 전체 레벨의 테스트를 하는 것은 큰 부담입니다.

+

ex) selenium을 활용한 UI 테스트 예시

+
import org.openqa.selenium.By;
+import org.openqa.selenium.Keys;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.firefox.FirefoxDriver;
+import org.openqa.selenium.support.ui.WebDriverWait;
+import static org.openqa.selenium.support.ui.ExpectedConditions.presenceOfElementLocated;
+import java.time.Duration;
+public class HelloSelenium {
+    public static void main(String[] args) {
+        WebDriver driver = new FirefoxDriver();
+        WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
+        try {
+            driver.get("https://google.com/ncr");
+            driver.findElement(By.name("q")).sendKeys("cheese" + Keys.ENTER);
+            WebElement firstResult = wait.until(presenceOfElementLocated(By.cssSelector("h3>div")));
+            System.out.println(firstResult.getAttribute("textContent"));
+        } finally {
+            driver.quit();
+        }
+    }
+}
+

API 레벨 인수 테스트

+

아무리 좋은 방법이라 하더라도 실무에 적용하기 힘들정도로 번거롭고 품질관리하기 어려우면 현실적으로 사용하기가 어렵습니다. +실무에서 ATDD를 통해 개발하기 위해서는 최대한 허들을 낮추어 많이 활용될 수 있도록 노력해야 합니다.

+

저는 UI 레벨에서의 인수 테스트가 아닌 API 레벨로 인수 테스트를 하는 것을 추천합니다. +인수 테스트를 위한 시나리오를 작성하고 이를 검증하는 인수 테스트를 API 레벨에서 만든 뒤 이를 구현하는 방법으로 진행하면 조금 수월하게 ATDD 사이클을 유지할 수 있습니다.

+

다음 포스팅에서 API 레벨의 인수 테스트 주도 개발 예시를 다루어보겠습니다.

\ No newline at end of file diff --git a/404.html b/404.html new file mode 100644 index 0000000..ff6c5b3 --- /dev/null +++ b/404.html @@ -0,0 +1,3 @@ +404: Not found :: 맨땅에 코딩

NOT FOUND

You just hit a route that doesn't exist... the sadness.

\ No newline at end of file diff --git a/404/index.html b/404/index.html new file mode 100644 index 0000000..9714af2 --- /dev/null +++ b/404/index.html @@ -0,0 +1,3 @@ +404: Not found :: 맨땅에 코딩

NOT FOUND

You just hit a route that doesn't exist... the sadness.

\ No newline at end of file diff --git a/Gemfile b/Gemfile deleted file mode 100644 index 3132452..0000000 --- a/Gemfile +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -source "https://rubygems.org" - -gem "tale" \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index ab2175d..0000000 --- a/Gemfile.lock +++ /dev/null @@ -1,71 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - addressable (2.7.0) - public_suffix (>= 2.0.2, < 5.0) - colorator (1.1.0) - concurrent-ruby (1.1.5) - em-websocket (0.5.1) - eventmachine (>= 0.12.9) - http_parser.rb (~> 0.6.0) - eventmachine (1.2.7) - ffi (1.11.2) - forwardable-extended (2.6.0) - http_parser.rb (0.6.0) - i18n (0.9.5) - concurrent-ruby (~> 1.0) - jekyll (3.8.6) - addressable (~> 2.4) - colorator (~> 1.0) - em-websocket (~> 0.5) - i18n (~> 0.7) - jekyll-sass-converter (~> 1.0) - jekyll-watch (~> 2.0) - kramdown (~> 1.14) - liquid (~> 4.0) - mercenary (~> 0.3.3) - pathutil (~> 0.9) - rouge (>= 1.7, < 4) - safe_yaml (~> 1.0) - jekyll-feed (0.10.0) - jekyll (~> 3.3) - jekyll-paginate (1.1.0) - jekyll-sass-converter (1.5.2) - sass (~> 3.4) - jekyll-seo-tag (2.5.0) - jekyll (~> 3.3) - jekyll-watch (2.2.1) - listen (~> 3.0) - kramdown (1.17.0) - liquid (4.0.3) - listen (3.2.0) - rb-fsevent (~> 0.10, >= 0.10.3) - rb-inotify (~> 0.9, >= 0.9.10) - mercenary (0.3.6) - pathutil (0.16.2) - forwardable-extended (~> 2.6) - public_suffix (4.0.1) - rb-fsevent (0.10.3) - rb-inotify (0.10.0) - ffi (~> 1.0) - rouge (3.13.0) - safe_yaml (1.0.5) - sass (3.7.4) - sass-listen (~> 4.0.0) - sass-listen (4.0.0) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - tale (0.1.6) - jekyll (~> 3.6) - jekyll-feed (~> 0.10.0) - jekyll-paginate (~> 1.1) - jekyll-seo-tag (~> 2.5.0) - -PLATFORMS - ruby - -DEPENDENCIES - tale - -BUNDLED WITH - 2.0.2 diff --git a/README.md b/README.md deleted file mode 100644 index e8976b7..0000000 --- a/README.md +++ /dev/null @@ -1 +0,0 @@ -# boorownie.github.io \ No newline at end of file diff --git a/_config.yml b/_config.yml deleted file mode 100644 index 11f758a..0000000 --- a/_config.yml +++ /dev/null @@ -1,32 +0,0 @@ -# Site settings -title: 맨땅에 코딩 -description: -url: https://boorownie.github.io -author: Boorownie - -# Google Analytics -google_analytics: UA-147207658-1 - -# Build settings -markdown: kramdown -include: - - _pages - -# Assets -sass: - sass_dir: _sass - style: compressed - -# Gems -plugins: - - jekyll-feed - - jekyll-paginate - - jekyll-seo-tag - # - jemoji #Uncomment this to allow emoji in your post - -# Permalinks -permalink: /:year-:month-:day/:title -paginate: 5 - -# Disqus (Set to your disqus id) -#disqus: jekyll-tale \ No newline at end of file diff --git a/_includes/analytics.html b/_includes/analytics.html deleted file mode 100644 index ffcfacd..0000000 --- a/_includes/analytics.html +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/_includes/footer.html b/_includes/footer.html deleted file mode 100644 index 5b95e1b..0000000 --- a/_includes/footer.html +++ /dev/null @@ -1,5 +0,0 @@ -
- - © {{ site.author }}. Powered by jekyll and Tale. - -
diff --git a/_includes/head.html b/_includes/head.html deleted file mode 100644 index 8bfe396..0000000 --- a/_includes/head.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - {% seo %} - - - - - - - - - - - - - - - {% if site.google_analytics and jekyll.environment == 'production' %} - {% include analytics.html %} - {% endif %} - diff --git a/_includes/navigation.html b/_includes/navigation.html deleted file mode 100644 index d858ec8..0000000 --- a/_includes/navigation.html +++ /dev/null @@ -1,11 +0,0 @@ - diff --git a/_layouts/default.html b/_layouts/default.html deleted file mode 100644 index 8c8261e..0000000 --- a/_layouts/default.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - {% include head.html %} - - - - {% include navigation.html %} - -
- {{ content }} -
- - {% include footer.html %} - - diff --git a/_layouts/home.html b/_layouts/home.html deleted file mode 100644 index 1bb3779..0000000 --- a/_layouts/home.html +++ /dev/null @@ -1,35 +0,0 @@ ---- -layout: default ---- - - - - diff --git a/_layouts/post.html b/_layouts/post.html deleted file mode 100644 index 4bf3fc1..0000000 --- a/_layouts/post.html +++ /dev/null @@ -1,43 +0,0 @@ ---- -layout: default ---- - -
- - -

{{ page.title }}

-
- - {{ content }} -
- - - - \ No newline at end of file diff --git a/_pages/about.md b/_pages/about.md deleted file mode 100644 index f28e20b..0000000 --- a/_pages/about.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -layout: post -title: "맨땅에 코딩" -author: "Boorownie" -permalink: /about/ ---- - -
이렇게 기록을 남기면 조금 덜 까먹겠지...
diff --git a/_posts/2019-11-27-agail_and_atdd.md b/_posts/2019-11-27-agail_and_atdd.md deleted file mode 100644 index a6154f8..0000000 --- a/_posts/2019-11-27-agail_and_atdd.md +++ /dev/null @@ -1,123 +0,0 @@ ---- -layout: post -title: "ATDD 프로세스" -author: "Boorownie" -tags: ["ATDD", "Agile"] ---- - -ATDD(Acceptance Test Driven Development)는, 용어를 그대로 풀어서 해석하면, `인수 테스트 주도 개발`으로서 -[인수 테스트](https://www.agilealliance.org/glossary/acceptance)를 먼저 작성한 다음 기능 개발을 하는 방법입니다. -여기서 말하는 `인수`는 클라이언트가 요청한 소프트웨어의 결과물을 넘겨 받는다는 의미이며, 인수 테스트는 요구사항을 모두 만족하여 기능 완료 여부를 확인하는 테스트라고 할 수 있습니다. - -단순히 인수 테스트를 만들기 위해 ATDD를 도입하는 것은 아닙니다. -ATDD의 뜻을 찾아보면 **사용자(고객)-개발자-테스터간의 커뮤니케이션을 기반한 개발 방법**이라고 정의됩니다. -커뮤니케이션을 위해서 인수 테스트를 만든다는 뜻인데 이 둘의 연관관계를 연상하는 것이 어려울 수 있습니다. -이번 포스팅에서는 인수 테스트를 만드는 ATDD의 궁극적인 목표와 ATDD 프로세스를 예시를 통해 알아보도록 하겠습니다. - --- -# 애자일 관점에서의 ATDD - - -애자일 방법론에서는 미리 예측하며 개발을 하지 않습니다. 일정한 주기를 가지고 끊임없이 프로토 타입을 만들어냅니다. -필요한 부분이 발생하면, 그때 기능 목록을 추가하고 수정하여 하나의 커다란 소프트웨어를 개발해 나갑니다.[[link]](https://en.wikipedia.org/wiki/Agile_software_development) -이러한 애자일의 프로그밍 방법론 중 하나인 ATDD는 [사용자 스토리](https://www.agilealliance.org/glossary/user-stories)를 기반으로 -[인수 조건](https://www.altexsoft.com/blog/business/acceptance-criteria-purposes-formats-and-best-practices/)을 도출하여 -기능 개발을 진행하는 방법론 입니다. - --- - - -ATDD에서는 사용자(고객)-개발자-테스터, 즉 팀원들이 각 포지션의 관점으로 함께 논의하여 실행 가능한 예제를 도출합니다. -이러한 과정은 사용자(고객)들에게 요구사항을 명확히 하는데 도움을 주고, 개발자에게는 코드 구현의 방향성과 목적을 가지는데 도움을 주고, 테스터에게는 단순히 기능적 테스트를 하는 것 보다 좀 더 나은 계획을 세울 수 있게 도와줍니다. -결국 ATDD는 팀원간에 요구사항을 **같은 수준으로 이해**하는데 큰 도움을 줍니다. -**요구사항이 무엇**이고 **기능 완료 기준이 무엇**인지에 대해 하나의 공통된 생각을 가질 수 있게 합니다. -일정한 주기를 가지고 진행하는 "스프린트"에서는 이러한 공통의 기준들이 매우 중요합니다.[[link]](https://codoid.com/acceptance-test-driven-development-atdd) - -> 즉, 스프린트에서는 팀원들이 공통의 기준을 가지고 진행하는것이 중요합니다. 사용자 스토리와 인수 조건, 인수 테스트 작성을 모든 개발 팀원이 함께 논의하여 요구사항에 대한 공통의 이해를 할 수 있게 도와줍니다. - --- -# ATDD 프로세스 -![](https://user-images.githubusercontent.com/4353846/69546748-aba00e00-0fd7-11ea-9d83-ad4a7b025832.png){: width="70%" height="70%"} - -ATDD는 4가지의 행위(Discuss, Distill, Develop, Demo)와 -4가지의 산출물(User Story, Acceptance Criteria, Tests, Working Software), -하나의 결과(Business Value)로 설명할 수 있습니다.[[link]](https://mysoftwarequality.wordpress.com/2013/11/12/when-something-works-share-it) -ATDD Cycle을 바탕으로 **서핑 용품 대여 서비스를 개발**해 나가는 과정을 따라가며 ATDD 프로세스에 대해서 알아보겠습니다. - --- -## 사용자 스토리 작성하기 -- 사용자 스토리는 who, why, what을 기준으로 작성합니다.[[link]](https://www.agilealliance.org/glossary/user-story-template) -- 결과물: 사용자 스토리 -> 서핑샵 손님들은 서핑 용품을 빌리기 위해 대여 신청을 하고 싶다. - -## Discuss -- 사용자 스토리를 바탕으로 예시만들고, 이를 통해 인수 조건을 도출합니다. [인수 조건 잘 도출하는 법](https://agileforgrowth.com/blog/acceptance-criteria-checklist/) -- 인수 조건은 기능 완료 조건으로서 사용자 스토리 보다 조금 더 구체적인 시나리오를 통해 기술합니다. -- 결과물: 인수 조건 -> - 날짜별로 대여가 가능한 용품을 조회할 수 있어야 한다. -> - 대여를 희망하는 용품을 희망하는 날짜에 신청 할 수 있어야 한다. -> - 대여 신청한 내역을 사장님이 확인 할 수 있어야 한다. - -## Distill -- 도출된 인수 조건을 통해 인수 테스트를 작성합니다. -- 인수 테스트는 사용자의 관점을 나타내며 시스템 작동 방식을 설명하기 위한 요구 사항의 형태로 작동하며 시스템이 의도 한대로 작동하는지 확인하는 방법으로도 사용됩니다. -- 테스트는 [Given-When-Then](https://www.agilealliance.org/glossary/gwt) 형식으로 작성을 하되 해당사항이 없는 부분은 생략할 수 있습니다. -- 성공 케이스와 실패 케이스를 정의하고 요청/응답에 대한 DTO를 결정합니다. -- 인수 테스트 작성의 기준은 팀에서 정합니다. (ex. 인수 테스트는 기능별로 만들고 Controller 테스트를 통해 에러 케이스를 검증한다.) -- 결과물: 인수 테스트 -> -```java -@DisplayName("날짜별로 대여가 가능한 용품을 조회할 수 있어야 한다.") -@Test -public void showItems() { - this.webTestClient.get() - .uri(uriBuilder -> uriBuilder.path("/items") - .queryParam("date", "20191127") - .build()) - .accept(MediaType.APPLICATION_JSON) - .exchange() - .expectStatus().isOk() - .expectHeader().contentType(MediaType.APPLICATION_JSON) - .expectBody() - .jsonPath("$").isNotEmpty(); -} -``` - -#### 테스트 시나리오의 품질관리 - - -기술적인 행위를 바탕으로 시나리오를 작성 할 경우 기술과 관련된 용어가 나타나고 구현과 관련된 기술이 바뀌거나 시스템을 사용하는 순서가 바뀌면 테스트 시나리오를 수정해야 합니다. -작업 흐름을 바탕으로 시나리오를 작성 할 경우 기술이 바뀌더라도 테스트 시나리오가 바뀌지 않지만, 작업 흐름이 변경되면 테스트 시나리오를 수정해야 합니다. -비즈니스 규칙을 명확히 드러나도록 테스트 시나리오를 작성하면 구현과 관련 기술이 변경되어도, 작업 흐름이 바뀌어도 테스트 시나리오를 변경하지 않아도 됩니다. -[[6]](https://gojko.net/2010/04/13/how-to-implement-ui-testing-without-shooting-yourself-in-the-foot-2) - -- Technical Activity -1. 대여 가능한 용품을 조회할 수 있는 페이지로 접속한다. -2. 조회하고자 하는 날짜를 "20191127"이라고 입력한다. -4. 조회하기 버튼을 누른다. -5. 결과 페이지에서 대여가능한 용품 목록을 확인한다. - -- Workflow -1. 대여 가능한 용품을 조회할 수 있는 페이지로 접속한다. -3. 조회하고자 하는 날짜를 2019년 11월 27일로 설정한다. -4. 조회한다. -5. 대여가능한 용품 목록 확인한다. - -- Business Rule - - Scenario: 특정 일자에 대여 가능한 물품 정보를 제공한다. - - When: 2019년 11월 27일 대여 가능한 물품을 조회한다. - - Then: 2019년 11월 27일 대여 가능한 물품 목록을 확인할 수 있다. - -## Develop -- 문서화 - - 벡엔드와 프론트의 작업을 병렬로 진행할 수 있도록 도움을 줍니다. - - 문서화 할 기능의 기준은 팀에서 정합니다. (ex. 사이드 케이스 / 실패 케이스에 대해서는 내부항목으로 기재한다.) -- TDD로 개발하기 - - 인수 테스트 기반으로 하나씩 기능개발을 진행합니다. - - TDD 프로세스에 따라 테스트를 먼저 작성하고 프로덕션 코드를 작성합니다. -- 결과물: 소프트웨어 결과물 -> 다음 포스팅을 통해 예시를 설명하겠습니다 :) - -## Demo -- 인수 테스트가 성공이 되면 해당 이슈는 완료합니다. (~~애자일에서는 검증 단계가 필요 없을까?~~) -- 완료된 인수 테스트 수에 따라서 개발 진행 상황을 인지할 수 있습니다. \ No newline at end of file diff --git a/_posts/2019-11-27-atdd_spring_boot.md b/_posts/2019-11-27-atdd_spring_boot.md deleted file mode 100644 index 684a71d..0000000 --- a/_posts/2019-11-27-atdd_spring_boot.md +++ /dev/null @@ -1,380 +0,0 @@ ---- -layout: post -title: "ATDD with Spring Boot" -author: "Boorownie" -tags: ["ATDD", "Spring", "Spring Boot"] ---- - -![](https://images.squarespace-cdn.com/content/v1/5bcdd76a81551217e12a7a2b/1541601663984-RX2K7WRMXZTYY4PGU9AE/ke17ZwdGBToddI8pDm48kOyctPanBqSdf7WQMpY1FsRZw-zPPgdn4jUwVcJE1ZvWQUxwkmyExglNqGp0IvTJZUJFbgE-7XRK3dMEBRBhUpyD4IQ_uEhoqbBUjTJFcqKvko9JlUzuVmtjr1UPhOA5qkTLSJODyitRxw8OQt1oetw/Hindsight_Outside+in+Development.png){: width="60%" height="60%"} - -TDD를 배우면서 도메인에 먼저 집중하여 도메인 기능의 테스트 코드를 구현한 후 기능을 구현하는 방법으로 학습합니다. -한 번에 한 가지를 집중하고 각 도메인들은 애플리케이션이 구성되기 전에 작성됩니다. -하지만 ATDD에서 인수 테스트는 큰 그림을 그려 구조를 형성한 다음 구체적인 부분을 구현합니다. -이러한 접근 방법을 전자는 `Inside out`이라 하고 후자는 `Outside in`이라고 합니다.(TDD가 Inside out이고 ATDD가 Outside in이라는 뜻은 아닙니다.) -TDD를 이미 접한 후 ATDD를 배우는 사람은 각 접근 방식의 차이점을 인지하지 못하여 학습에 어려움을 겪기도 합니다. -Inside out 접근 방식은 도메인 지식이 있거나 요구 사항이 단순한 경우 적합하고 -Outside in 접근 방식은 도메인 지식이 없거나 요구 사항 복잡도가 높은 경우 적합하다 할 수 있습니다. -각 방식의 장단점은 [TDD - From the Inside Out or the Outside In?](https://8thlight.com/blog/georgina-mcfadyen/2016/06/27/inside-out-tdd-vs-outside-in.html) 에서 확인할 수 있습니다. -이번 포스팅에서는 간단한 예제를 통해 Outside in방식의 ATDD 개발 방법에 대해서 알아보겠습니다. - --- -# ATDD 개발 프로세스 -![](https://user-images.githubusercontent.com/4353846/67550967-c108ea80-f742-11e9-9f06-1b39fe4ba0f8.png){: width="60%" height="60%"} -1. 인수 조건을 바탕으로 인수 테스트 작성한다. -2. 인수 테스트를 바탕으로 문서화를 하여 인터페이스를 공유한다. -3. 인수 테스트를 동작시키는 기능을 하나씩 구현한다. - --- -# [예제] 서핑 용품 대여 신청 기능 개발 -> 상황에 따라 테스트 방법이 달라질 수 있고 아래의 예시는 그 방법 중 하나입니다. - -서핑 용품 대여 신청 기능을 구현하는 과정을 ATDD 개발 프로세스로 단계별로 알아보겠습니다. -예제에서는 WebTestClient로 Happy case에 대한 인수 테스트를 만들고 -예외적인 상황이나 에러 처리 테스트는 Controller Test의 MockMvc객체를 이용할 예정입니다. -(추후 Github 공유 예정) - -## 인수 조건 -> 대여를 희망하는 용품을 희망하는 날짜에 신청 할 수 있어야 한다. - -## 인수 테스트 작성 -이전 포스팅에서 언급되었던 것 처럼, 비즈니스 규칙(Business Rule)은 기술의 구현이나 작업흐름만큼 변경이 많지 않습니다. -인수 테스트를 작성할 때 비즈니스 규칙을 고려하여 테스트 시나리오를 만들 경우 변경사항에 비교적으로 유연하게 대처할 수 있습니다. -그런 관점에서 볼 때, UI 인수 테스트를 작성하는 것 보다 API 인수 테스트를 하는게 시나리오의 품질관리에 유리합니다. -따라서 UI 테스트 보다는 API 테스트를 통해 인수 테스트를 작성하겠습니다. - -```java -@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@AutoConfigureWebTestClient -public class RentalAcceptanceTest { - public static final String LOCATION = "Location"; - - @Autowired - private WebTestClient webTestClient; - - @Test - public void createRental() { - String inputJson = "{\"dateTime\":\"" + "2019120112" + "\", " + - "\"itemId\":\"" + "110920" + "\", " + - "\"itemType\":\"" + "14" + "\"}"; - - this.webTestClient.post().uri("/rentals") - .contentType(MediaType.APPLICATION_JSON) // 요청으로 보내는 데이터 유형 명시 - .accept(MediaType.APPLICATION_JSON) // 응답으로 받고 싶은 데이터 유형 명시 - .body(Mono.just(inputJson), String.class) - .exchange() - .expectStatus().isCreated() - .expectHeader().contentType(MediaType.APPLICATION_JSON) - .expectHeader().valueMatches(LOCATION, "\\/rentals\\/\\d") - .expectBody() - .jsonPath("$").isNotEmpty() - .jsonPath("$.id").isNotEmpty() - .jsonPath("$.status").isEqualTo("READY"); - } -} -``` - --- -## 문서화 -Mock 서버를 먼저 개발하여 Mock 데이터를 제공하면 백엔드와 프론트엔드 개발이 병렬적으로 진행이 가능합니다. -이 때, 인수 테스트를 기반으로 문서화까지 해서 제공한다면 더 효과적인 커뮤니케이션이 가능합니다. -따라서 [Spring Rest Docs](https://spring.io/projects/spring-restdocs)를 이용하여 문서를 만들 예정입니다. -Rest Docs를 이용하여 문서를 만들기 위해서는 테스트가 성공되어야 합니다. -따라서 인수 테스트를 성공시키기 위한 Mock 서버를 구현하고 Request / Response DTO를 선언합니다. -이 부분은 ATDD 프로세스에 위배될 수 있지만 업무 효율을 고려하여 유리하다고 판단하여 이렇게 결정하였습니다. - -#### Mock 서버 구현 -```java -@RestController -public class RentalController { - @PostMapping("/rentals") - public ResponseEntity createArticles(@RequestBody RentalRequestDto requestDto) { - return ResponseEntity.created(URI.create("/rentals/" + 1)).body(new RentalResponseDto(1, "READY")); - } -} -``` - -#### Rest Docs 문서 설정 -Rest Docs를 통해 문서화를 할 경우 다음과 같은 객체를 사용하는 것을 권장합니다, MockMvc / WebTestClient / RestAssured -각 테스트 객체의 특징을 고려하여 선택한 후 문서화에 사용하면 좋을 것 같습니다. -상황에 따라서 여러 종류의 테스트 객체를 사용해야 할 수 도 있습니다. - -참고 문서 -- [Spring REST Docs](https://docs.spring.io/spring-restdocs/docs/2.0.4.RELEASE/reference/html5/) -- [Spring Rest Docs 적용](http://woowabros.github.io/experience/2018/12/28/spring-rest-docs.html) - --- -## TDD 기능 구현 -#### Controller TDD -Controller의 메서드에서 구현할 내용에 대해서 given을 통해 응답을 정의합니다. -작업 순서는 Controller Test를 작성하면서 Service 클래스를 생성하여 빈 껍데기 메서드만 만든 뒤 Controller 프로덕션 코드를 작성합니다. -Controller 테스트에서 MockMvc 객체를 사용한 이유는 Interceptor나 ArgumentResolver와 같이 Controller 이전에 동작하는 기능 등 전체 로직을 통합 테스트하기 위해서 입니다. -만약 Controller나 Interceptor, ArgumentResolver 와 같은 객체들을 단위테스트 할 경우, 해당 객체를 생성하여 메서드를 바로 호출하는 테스트를 작성해도 무관합니다. -RentalService의 createRental 메서드에서는 Item조회를 하고 Item이 유효한지 확인한 후 Rental을 만들어 저장하는 로직을 구현할 예정입니다. - -아래 코드의 개발 순서는 - -```java -@WebMvcTest(controllers = RentalController.class) -@AutoConfigureMockMvc -public class RentalControllerTest { - @Autowired - private MockMvc mockMvc; - - // 3 - RentalService 객체를 만들고 createRental메서드 생성 - @MockBean - private RentalService rentalService; - - @DisplayName("대여 신청을 한다.") - @Test - public void createRental() throws Exception { - - // 5 - ItemTest와 RentalTest를 생성하고 생성하는 로직을 구현 - Item item = new Item(110920, "READY"); - Rental rental = new Rental(1, "20191127", item, "READY"); - - // 2 - item 존재 여부확인 + rental 저장 로직을 service 객체로 분리하기로 결정 - given(rentalService.createRental(any())).willReturn(rental); - - // 1 - String inputJson = "{\"date\":\"" + "20191127" + "\", " + - "\"itemId\":\"" + "110920" + "\"}"; - mockMvc.perform(post("/rentals") - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON) - .content(inputJson)) - .andExpect(status().isCreated()) - .andExpect(header().exists("Location")) - .andExpect(jsonPath("$.id").isNotEmpty()) - .andExpect(jsonPath("$.date").value("20191127")) - .andExpect(jsonPath("$.itemId").value("110920")) - .andExpect(jsonPath("$.status").value("READY")) - .andDo(print()); - } -} - ---- - -// 4 -@Service -public class RentalService { - public Rental createRental(RentalRequestDto requestDto) { - return null; - } -} - ---- - -// 6 -@RestController -public class RentalController { - private RentalService rentalService; - - public RentalController(RentalService rentalService) { - this.rentalService = rentalService; - } - - @PostMapping("/rentals") - public ResponseEntity createArticles(@RequestBody RentalRequestDto requestDto) { - Rental persistRental = rentalService.createRental(requestDto); - return ResponseEntity.created(URI.create("/rentals/" + persistRental.getId())).body(persistRental); - } -} - -``` - -#### Service TDD -Controller Test에서 given으로 정의한 Service 메서드를 구현합니다. Controller와 마찬가지로 테스트를 먼저 작성한 뒤 프로덕션 코드를 작성합니다. -Service 레이어를 테스트 할 때 반드시 Repository를 MockBean으로 간주하여 단위테스트를 해야하는 건 아니며 트랙젝션 등 처리로직 확인을 위해서는 통합테스트로 진행해도 무관합니다. - -```java -@SpringBootTest(classes = RentalService.class) -public class RentalServiceTest { - private RentalService rentalService; - - // 4 - @MockBean - private RentalRepository rentalRepository; - @MockBean - private ItemRepository itemRepository; - - @BeforeEach - void setUp() { - rentalService = new RentalService(rentalRepository, itemRepository); - } - - @Test - void createRental() { - Item item = new Item(1, "READY"); - - // 3 - given(itemRepository.findById(anyInt())).willReturn(item); - - RentalRequestDto rentalRequestDto = new RentalRequestDto("20191127", 1123); - - // 2 - given(rentalRepository.save(any())).willReturn(new Rental(1, rentalRequestDto.getDate(), item, "READY")); - - // 1 - Rental persistRental = rentalService.createRental(rentalRequestDto); - - assertThat(persistRental).isNotNull(); - } -} - ---- - -// 5 -@Service -public class RentalService { - private RentalRepository rentalRepository; - private ItemRepository itemRepository; - - public RentalService(RentalRepository rentalRepository, ItemRepository itemRepository) { - this.rentalRepository = rentalRepository; - this.itemRepository = itemRepository; - } - - public Rental createRental(RentalRequestDto requestDto) { - Item persistItem = itemRepository.findById(requestDto.getItemId()); - Rental persistRental = new Rental(requestDto.getDate(), persistItem, "READY"); - return rentalRepository.save(persistRental); - } -} - - -``` - -#### Repository TDD -```java -@SpringBootTest(classes = RentalRepository.class) -public class RentalRepositoryTest { - private RentalRepository rentalRepository; - - @BeforeEach - void setUp() { - rentalRepository = new RentalRepository(); - } - - @Test - void save() { - // 2 - Item item = new Item(110920, "READY"); - Rental rental = new Rental("20191127", item, "READY"); - - // 1 - Rental persistRental = rentalRepository.save(rental); - - assertThat(persistRental.getId()).isEqualTo(1); - } -} - ---- - -@Repository -public class RentalRepository { - private List rentals = new ArrayList<>(); - - public Rental save(Rental rental) { - Rental persistRental = new Rental(rentals.size() + 1, rental.getDate(), rental.getItem(), rental.getStatus()); - rentals.add(persistRental); - return persistRental; - } -} - -``` - -```java -// ItemRepository도 같은 방식으로 진행 - -``` - -#### Controller TDD - 예외 케이스 -정상적인 로직에 대한 테스트 작성이 끝나면 Side Case에 대한 테스트를 작성해야합니다. -Happy Case에 대한 테스트를 작성하는 것 처럼 테스트 케이스에 대한 메서드를 만들어주고 given 조건과 기대하는 then 조건을 설정합니다. - -참고로 **도메인 클래스 작성은 어느 레이어에서 해도 상관이 없다**고 생각합니다. -Outside in 이라고 해서 반드시 그 방향대로(ex. Controller -> Service -> Repository 순) 개발을 해야하는 것이 아니라 -테스트를 구현하고 로직을 구현하다가 **필요한 로직이 생기면 그 때** 해당 부분을 구현하기 위해 테스트 코드를 구현하고 로직을 구현하였습니다. - -```java -@DisplayName("대여 신청 시 아이템이 이미 대여중인 경우 400에러를 응답한다.") -@Test -public void createRentalWithInvalidItemId() throws Exception { - given(rentalService.createRental(any())).willThrow(new InvalidItemException()); - - String inputJson = "{\"date\":\"" + "20191127" + "\", " + - "\"itemId\":\"" + "111" + "\"}"; - - mockMvc.perform(post("/rentals") - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON) - .content(inputJson)) - .andExpect(status().isBadRequest()) - .andDo(print()); -} - ---- - -@ResponseStatus(HttpStatus.BAD_REQUEST) -public class InvalidItemException extends RuntimeException{ -} - ---- - -public class RentalTest { - @Test() - void checkItem() { - Assertions.assertThrows(AlreadyRentItemException.class, () -> { - new Rental("20191127", new Item(111, "RENT"), "READY"); - }); - } -} - ---- - -public class Rental { - ... - - public Rental(String date, Item item, String status) { - item.checkStatus(); - this.date = date; - this.item = item; - this.status = status; - } - - ... -} - ---- - -public class ItemTest { - @Test - void checkStatus() { - Item item = new Item(111, "RENT"); - Assertions.assertThrows(AlreadyRentItemException.class, () -> { - item.checkStatus(); - }); - } -} - - -public class Item { - ... - - public void checkStatus() { - if (status.equals("RENT")) { - throw new AlreadyRentItemException(); - } - } -} - -``` - -#### 리팩터링 -추후 업로드 예정;;; - --- -## 인수 테스트 동작 확인 -TDD 프로세스를 통해 기능 구현이 끝나면 최초 작성한 인수 테스트가 정상적으로 동작하는지 확인을 합니다. -최초에 인수 테스트가 성공한 것은 Mock 서버가 동작하고 있었기 때문인데 이 기능을 실제 기능 구현으로 대체하게 되었습니다. diff --git a/_sass/tale.scss b/_sass/tale.scss deleted file mode 100644 index 3cdfa99..0000000 --- a/_sass/tale.scss +++ /dev/null @@ -1,8 +0,0 @@ -@import 'tale/variables'; -@import 'tale/base'; -@import 'tale/code'; -@import 'tale/post'; -@import 'tale/syntax'; -@import 'tale/layout'; -@import 'tale/pagination'; -@import 'tale/catalogue'; diff --git a/_sass/tale/_base.scss b/_sass/tale/_base.scss deleted file mode 100644 index 93e0a52..0000000 --- a/_sass/tale/_base.scss +++ /dev/null @@ -1,70 +0,0 @@ -* { - @include box-sizing; - line-height: 1.5; -} - -html, -body { - color: $default-color; - margin: 0; - padding: 0; -} - -html { - font-family: $serif-primary; - font-size: 14px; - overflow-y: scroll; - - @media (min-width: 600px) { - font-size: 16px; - } -} - -body { - -webkit-text-size-adjust: 100%; -} - -h1, -h2, -h3, -h4, -h5, -h6 { - color: $default-shade; - font-family: $sans-serif; - line-height: normal; -} - -a { - color: $blue; - text-decoration: none; -} - -blockquote { - border-left: .25rem solid $grey-2; - color: $grey-1; - margin: .8rem 0; - padding: .5rem 1rem; - - p:last-child { - margin-bottom: 0; - } - - @media (min-width: 600px) { - padding: 0 5rem 0 1.25rem; - } -} - -img { - display: block; - margin: 0 0 1rem; - max-width: 100%; -} - -td { - vertical-align: top; -} - -strong { - background-color: blanchedalmond; -} \ No newline at end of file diff --git a/_sass/tale/_catalogue.scss b/_sass/tale/_catalogue.scss deleted file mode 100644 index 0587bc5..0000000 --- a/_sass/tale/_catalogue.scss +++ /dev/null @@ -1,39 +0,0 @@ -.catalogue { - &-item { - border-bottom: 1px solid $grey-2; - color: $default-color; - display: block; - padding: 2rem 0; - - &:hover .catalogue-line, - &:focus .catalogue-line { - width: 5rem; - } - - &:last-child { - border: 0; - } - } - - &-time { - color: $default-tint; - font-family: $serif-secondary; - letter-spacing: .5px; - } - - &-title { - color: $default-shade; - display: block; - font-family: $sans-serif; - font-size: 2rem; - font-weight: 700; - margin: .5rem 0; - } - - &-line { - @include transition(all .3s ease-out); - border-top: .2rem solid $default-shade; - display: block; - width: 2rem; - } -} diff --git a/_sass/tale/_code.scss b/_sass/tale/_code.scss deleted file mode 100644 index 1597e13..0000000 --- a/_sass/tale/_code.scss +++ /dev/null @@ -1,46 +0,0 @@ -pre, -code { - font-family: $monospaced; -} - -code { - background-color: $grey-3; - border-radius: 3px; - color: $code-color; - font-size: 85%; - padding: .25em .5em; -} - -pre { - margin: 0 0 1rem; -} - -pre code { - background-color: transparent; - color: inherit; - font-size: 100%; - padding: 0; -} - -.highlight { - background-color: $grey-3; - border-radius: 3px; - line-height: 1.4; - margin: 0 0 1rem; - padding: 1rem; - - pre { - margin-bottom: 0; - overflow-x: auto; - } - - .lineno { - color: $default-tint; - display: inline-block; // Ensures the null space also isn't selectable - padding: 0 .75rem 0 .25rem; - // Make sure numbers aren't selectable - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; - } -} diff --git a/_sass/tale/_layout.scss b/_sass/tale/_layout.scss deleted file mode 100644 index ba4a7b0..0000000 --- a/_sass/tale/_layout.scss +++ /dev/null @@ -1,92 +0,0 @@ -.container { - margin: 0 auto; - max-width: 800px; - width: 80%; -} - -main, -footer, -.nav-container { - display: block; - margin: 0 auto; - max-width: 800px; - width: 80%; -} - -.nav { - box-shadow: 0 2px 2px -2px $shadow-color; - overflow: auto; - - &-container { - margin: 1rem auto; - position: relative; - text-align: center; - } - - &-title { - @include transition(all .2s ease-out); - color: $default-color; - display: inline-block; - margin: 0; - padding-right: .2rem; - - &:hover, - &:focus { - opacity: .6; - } - } - - ul { - list-style-type: none; - margin: 1rem 0 0; - padding: 0; - text-align: center; - } - - li { - @include transition(all .2s ease-out); - color: $default-color; - display: inline-block; - opacity: .6; - padding: 0 2rem 0 0; - - &:last-child { - padding-right: 0; - } - - &:hover, - &:focus { - opacity: 1; - } - } - - a { - color: $default-color; - font-family: $sans-serif; - } -} - -@media (min-width: 600px) { - .nav { - &-container { - text-align: left; - } - - ul { - bottom: 0; - position: absolute; - right: 0; - } - } -} - -footer { - font-family: $serif-secondary; - padding: 2rem 0; - text-align: center; - - span { - color: $default-color; - font-size: .8rem; - } -} diff --git a/_sass/tale/_pagination.scss b/_sass/tale/_pagination.scss deleted file mode 100644 index 3700e15..0000000 --- a/_sass/tale/_pagination.scss +++ /dev/null @@ -1,44 +0,0 @@ -.pagination { - border-top: .5px solid $grey-2; - font-family: $serif-secondary; - padding-top: 2rem; - position: relative; - text-align: center; - - span { - color: $default-shade; - font-size: 1.1rem; - } - - .top { - @include transition(all .3s ease-out); - color: $default-color; - font-family: $sans-serif; - font-size: 1.1rem; - opacity: .6; - - &:hover { - opacity: 1; - } - } - - .arrow { - @include transition(all .3s ease-out); - color: $default-color; - position: absolute; - - &:hover, - &:focus { - opacity: .6; - text-decoration: none; - } - } - - .left { - left: 0; - } - - .right { - right: 0; - } -} diff --git a/_sass/tale/_post.scss b/_sass/tale/_post.scss deleted file mode 100644 index 3680d80..0000000 --- a/_sass/tale/_post.scss +++ /dev/null @@ -1,63 +0,0 @@ -.post { - padding: 3rem 0; - - &-info { - color: $default-tint; - font-family: $serif-secondary; - letter-spacing: 0.5px; - text-align: center; - - span { - font-style: italic; - } - } - - &-title { - color: $default-shade; - font-family: $sans-serif; - font-size: 4rem; - margin: 1rem 0; - text-align: center; - } - - &-line { - border-top: 0.4rem solid $default-shade; - display: block; - margin: 0 auto 3rem; - width: 4rem; - } - - p { - margin: 0 0 1rem; - text-align: justify; - } - - a:hover { - text-decoration: underline; - } - - img { - margin: 0 auto 0.5rem; - } - - img + em { - color: $default-tint; - display: block; - font-family: $sans-serif; - font-size: 0.9rem; - font-style: normal; - text-align: center; - } - - // CSS for making emoji inline - img.emoji { - display: inline-block; - left: 0; - transform: none; - width: 1rem; - height: 1rem; - vertical-align: text-top; - padding: 0; - margin: 0; - } -} diff --git a/_sass/tale/_syntax.scss b/_sass/tale/_syntax.scss deleted file mode 100644 index 15ad797..0000000 --- a/_sass/tale/_syntax.scss +++ /dev/null @@ -1,65 +0,0 @@ -.highlight .hll { background-color: #ffc; } -.highlight .c { color: #999; } /* Comment */ -.highlight .err { color: #a00; background-color: #faa } /* Error */ -.highlight .k { color: #069; } /* Keyword */ -.highlight .o { color: #555 } /* Operator */ -.highlight .cm { color: #09f; font-style: italic } /* Comment.Multiline */ -.highlight .cp { color: #099 } /* Comment.Preproc */ -.highlight .c1 { color: #999; } /* Comment.Single */ -.highlight .cs { color: #999; } /* Comment.Special */ -.highlight .gd { background-color: #fcc; border: 1px solid #c00 } /* Generic.Deleted */ -.highlight .ge { font-style: italic } /* Generic.Emph */ -.highlight .gr { color: #f00 } /* Generic.Error */ -.highlight .gh { color: #030; } /* Generic.Heading */ -.highlight .gi { background-color: #cfc; border: 1px solid #0c0 } /* Generic.Inserted */ -.highlight .go { color: #aaa } /* Generic.Output */ -.highlight .gp { color: #009; } /* Generic.Prompt */ -.highlight .gs { } /* Generic.Strong */ -.highlight .gu { color: #030; } /* Generic.Subheading */ -.highlight .gt { color: #9c6 } /* Generic.Traceback */ -.highlight .kc { color: #069; } /* Keyword.Constant */ -.highlight .kd { color: #069; } /* Keyword.Declaration */ -.highlight .kn { color: #069; } /* Keyword.Namespace */ -.highlight .kp { color: #069 } /* Keyword.Pseudo */ -.highlight .kr { color: #069; } /* Keyword.Reserved */ -.highlight .kt { color: #078; } /* Keyword.Type */ -.highlight .m { color: #f60 } /* Literal.Number */ -.highlight .s { color: #d44950 } /* Literal.String */ -.highlight .na { color: #4f9fcf } /* Name.Attribute */ -.highlight .nb { color: #366 } /* Name.Builtin */ -.highlight .nc { color: #0a8; } /* Name.Class */ -.highlight .no { color: #360 } /* Name.Constant */ -.highlight .nd { color: #99f } /* Name.Decorator */ -.highlight .ni { color: #999; } /* Name.Entity */ -.highlight .ne { color: #c00; } /* Name.Exception */ -.highlight .nf { color: #c0f } /* Name.Function */ -.highlight .nl { color: #99f } /* Name.Label */ -.highlight .nn { color: #0cf; } /* Name.Namespace */ -.highlight .nt { color: #2f6f9f; } /* Name.Tag */ -.highlight .nv { color: #033 } /* Name.Variable */ -.highlight .ow { color: #000; } /* Operator.Word */ -.highlight .w { color: #bbb } /* Text.Whitespace */ -.highlight .mf { color: #f60 } /* Literal.Number.Float */ -.highlight .mh { color: #f60 } /* Literal.Number.Hex */ -.highlight .mi { color: #f60 } /* Literal.Number.Integer */ -.highlight .mo { color: #f60 } /* Literal.Number.Oct */ -.highlight .sb { color: #c30 } /* Literal.String.Backtick */ -.highlight .sc { color: #c30 } /* Literal.String.Char */ -.highlight .sd { color: #c30; font-style: italic } /* Literal.String.Doc */ -.highlight .s2 { color: #c30 } /* Literal.String.Double */ -.highlight .se { color: #c30; } /* Literal.String.Escape */ -.highlight .sh { color: #c30 } /* Literal.String.Heredoc */ -.highlight .si { color: #a00 } /* Literal.String.Interpol */ -.highlight .sx { color: #c30 } /* Literal.String.Other */ -.highlight .sr { color: #3aa } /* Literal.String.Regex */ -.highlight .s1 { color: #c30 } /* Literal.String.Single */ -.highlight .ss { color: #fc3 } /* Literal.String.Symbol */ -.highlight .bp { color: #366 } /* Name.Builtin.Pseudo */ -.highlight .vc { color: #033 } /* Name.Variable.Class */ -.highlight .vg { color: #033 } /* Name.Variable.Global */ -.highlight .vi { color: #033 } /* Name.Variable.Instance */ -.highlight .il { color: #f60 } /* Literal.Number.Integer.Long */ - -.css .o, -.css .o + .nt, -.css .nt + .nt { color: #999; } diff --git a/_sass/tale/_variables.scss b/_sass/tale/_variables.scss deleted file mode 100644 index 3f6105f..0000000 --- a/_sass/tale/_variables.scss +++ /dev/null @@ -1,29 +0,0 @@ -// Colors -$default-color: #555; -$default-shade: #353535; -$default-tint: #aaa; -$grey-1: #979797; -$grey-2: #e5e5e5; -$grey-3: #f9f9f9; -$white: #fff; -$blue: #4a9ae1; -$shadow-color: rgba(0, 0, 0, .2); -$code-color: #bf616a; - -// Fonts -$serif-primary: 'BMHANNAAir', 'Libre Baskerville', 'Times New Roman', Times, serif; -$serif-secondary: Palatino, 'Palatino LT STD', 'Palatino Linotype', 'Book Antiqua', 'Georgia', serif; -$sans-serif: 'BMJUA', 'Helvetica Neue', 'Segoe UI', Helvetica, Arial, sans-serif; -$monospaced: Menlo, Monaco, monospace; - -@mixin box-sizing($type: border-box) { - -webkit-box-sizing: $type; - -moz-box-sizing: $type; - box-sizing: $type; -} - -@mixin transition($args...) { - -webkit-transition: $args; - -moz-transition: $args; - transition: $args; -} diff --git a/about/index.html b/about/index.html new file mode 100644 index 0000000..4b56c0d --- /dev/null +++ b/about/index.html @@ -0,0 +1,14 @@ +About :: 맨땅에 코딩

About

19 July 2018 — Written by Radek

Hi there

+

My name is Radek and I'm the author of this starter. I made it to help you present your ideas easier.

+

We all know how hard is to start something on the web, especially these days. You need to prepare a bunch of stuff, configure them and when that’s done — create the content.

+

This starter is pretty basic and covers all of the essentials. All you have to do is start typing!

+

The starter includes:

+ +

So, there you have it... enjoy!

\ No newline at end of file diff --git a/app-0b7bd45e5a011a8d5b94.js b/app-0b7bd45e5a011a8d5b94.js new file mode 100644 index 0000000..088c760 --- /dev/null +++ b/app-0b7bd45e5a011a8d5b94.js @@ -0,0 +1,3 @@ +/*! For license information please see app-0b7bd45e5a011a8d5b94.js.LICENSE.txt */ +(window.webpackJsonp=window.webpackJsonp||[]).push([[4],{"+ZDr":function(t,e,n){"use strict";var r=n("TqRt");e.__esModule=!0,e.withPrefix=d,e.withAssetPrefix=function(t){return d(t,v())},e.navigateTo=e.replace=e.push=e.navigate=e.default=void 0;var o=r(n("8OQS")),i=r(n("pVnL")),a=r(n("PJYZ")),u=r(n("VbXa")),c=r(n("17x9")),s=r(n("q1tI")),l=n("YwZP"),f=n("LYrO"),p=n("cu4x");e.parsePath=p.parsePath;var h=function(t){return null==t?void 0:t.startsWith("/")};function d(t,e){var n,r;if(void 0===e&&(e=g()),!m(t))return t;if(t.startsWith("./")||t.startsWith("../"))return t;var o=null!==(n=null!==(r=e)&&void 0!==r?r:v())&&void 0!==n?n:"/";return""+((null==o?void 0:o.endsWith("/"))?o.slice(0,-1):o)+(t.startsWith("/")?t:"/"+t)}var v=function(){return""},g=function(){return""},m=function(t){return t&&!t.startsWith("http://")&&!t.startsWith("https://")&&!t.startsWith("//")};var y=function(t,e){return"number"==typeof t?t:m(t)?h(t)?d(t):function(t,e){return h(t)?t:(0,f.resolve)(t,e)}(t,e):t},b={activeClassName:c.default.string,activeStyle:c.default.object,partiallyActive:c.default.bool},w=function(t){function e(e){var n;(n=t.call(this,e)||this).defaultGetProps=function(t){var e=t.isPartiallyCurrent,r=t.isCurrent;return(n.props.partiallyActive?e:r)?{className:[n.props.className,n.props.activeClassName].filter(Boolean).join(" "),style:(0,i.default)({},n.props.style,n.props.activeStyle)}:null};var r=!1;return"undefined"!=typeof window&&window.IntersectionObserver&&(r=!0),n.state={IOSupported:r},n.handleRef=n.handleRef.bind((0,a.default)(n)),n}(0,u.default)(e,t);var n=e.prototype;return n.componentDidUpdate=function(t,e){this.props.to===t.to||this.state.IOSupported||___loader.enqueue((0,p.parsePath)(y(this.props.to,window.location.pathname)).pathname)},n.componentDidMount=function(){this.state.IOSupported||___loader.enqueue((0,p.parsePath)(y(this.props.to,window.location.pathname)).pathname)},n.componentWillUnmount=function(){if(this.io){var t=this.io,e=t.instance,n=t.el;e.unobserve(n),e.disconnect()}},n.handleRef=function(t){var e,n,r,o=this;this.props.innerRef&&this.props.innerRef.hasOwnProperty("current")?this.props.innerRef.current=t:this.props.innerRef&&this.props.innerRef(t),this.state.IOSupported&&t&&(this.io=(e=t,n=function(){___loader.enqueue((0,p.parsePath)(y(o.props.to,window.location.pathname)).pathname)},(r=new window.IntersectionObserver((function(t){t.forEach((function(t){e===t.target&&(t.isIntersecting||t.intersectionRatio>0)&&(r.unobserve(e),r.disconnect(),n())}))}))).observe(e),{instance:r,el:e}))},n.render=function(){var t=this,e=this.props,n=e.to,r=e.getProps,a=void 0===r?this.defaultGetProps:r,u=e.onClick,c=e.onMouseEnter,f=(e.activeClassName,e.activeStyle,e.innerRef,e.partiallyActive,e.state),h=e.replace,d=(0,o.default)(e,["to","getProps","onClick","onMouseEnter","activeClassName","activeStyle","innerRef","partiallyActive","state","replace"]);return s.default.createElement(l.Location,null,(function(e){var r=e.location,o=y(n,r.pathname);return m(o)?s.default.createElement(l.Link,(0,i.default)({to:o,state:f,getProps:a,innerRef:t.handleRef,onMouseEnter:function(t){c&&c(t),___loader.hovering((0,p.parsePath)(o).pathname)},onClick:function(e){if(u&&u(e),!(0!==e.button||t.props.target||e.defaultPrevented||e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)){e.preventDefault();var n=h,r=encodeURI(o)===window.location.pathname;"boolean"!=typeof h&&r&&(n=!0),window.___navigate(o,{state:f,replace:n})}return!0}},d)):s.default.createElement("a",(0,i.default)({href:o},d))}))},e}(s.default.Component);w.propTypes=(0,i.default)({},b,{onClick:c.default.func,to:c.default.string.isRequired,replace:c.default.bool,state:c.default.object});var x=function(t,e,n){return console.warn('The "'+t+'" method is now deprecated and will be removed in Gatsby v'+n+'. Please use "'+e+'" instead.')},S=s.default.forwardRef((function(t,e){return s.default.createElement(w,(0,i.default)({innerRef:e},t))}));e.default=S;e.navigate=function(t,e){window.___navigate(y(t,window.location.pathname),e)};var R=function(t){x("push","navigate",3),window.___push(y(t,window.location.pathname))};e.push=R;e.replace=function(t){x("replace","navigate",3),window.___replace(y(t,window.location.pathname))};e.navigateTo=function(t){return x("navigateTo","navigate",3),R(t)}},"/GqU":function(t,e,n){var r=n("RK3t"),o=n("HYAF");t.exports=function(t){return r(o(t))}},"/b8u":function(t,e,n){var r=n("STAE");t.exports=r&&!Symbol.sham&&"symbol"==typeof Symbol.iterator},"/byt":function(t,e){t.exports={CSSRuleList:0,CSSStyleDeclaration:0,CSSValueList:0,ClientRectList:0,DOMRectList:0,DOMStringList:0,DOMTokenList:1,DataTransferItemList:0,FileList:0,HTMLAllCollection:0,HTMLCollection:0,HTMLFormElement:0,HTMLSelectElement:0,MediaList:0,MimeTypeArray:0,NamedNodeMap:0,NodeList:1,PaintRequestList:0,Plugin:0,PluginArray:0,SVGLengthList:0,SVGNumberList:0,SVGPathSegList:0,SVGPointList:0,SVGStringList:0,SVGTransformList:0,SourceBufferList:0,StyleSheetList:0,TextTrackCueList:0,TextTrackList:0,TouchList:0}},"/hTd":function(t,e,n){"use strict";e.__esModule=!0,e.SessionStorage=void 0;var r=function(){function t(){}var e=t.prototype;return e.read=function(t,e){var n=this.getStateKey(t,e);try{var r=window.sessionStorage.getItem(n);return r?JSON.parse(r):0}catch(o){return window&&window.___GATSBY_REACT_ROUTER_SCROLL&&window.___GATSBY_REACT_ROUTER_SCROLL[n]?window.___GATSBY_REACT_ROUTER_SCROLL[n]:0}},e.save=function(t,e,n){var r=this.getStateKey(t,e),o=JSON.stringify(n);try{window.sessionStorage.setItem(r,o)}catch(i){window&&window.___GATSBY_REACT_ROUTER_SCROLL||(window.___GATSBY_REACT_ROUTER_SCROLL={}),window.___GATSBY_REACT_ROUTER_SCROLL[r]=JSON.parse(o)}},e.getStateKey=function(t,e){var n="@@scroll|"+t.pathname;return null==e?n:n+"|"+e},t}();e.SessionStorage=r},"/qmn":function(t,e,n){var r=n("2oRo");t.exports=r.Promise},"07d7":function(t,e,n){var r=n("AO7/"),o=n("busE"),i=n("sEFX");r||o(Object.prototype,"toString",i,{unsafe:!0})},"0BK2":function(t,e){t.exports={}},"0Dky":function(t,e){t.exports=function(t){try{return!!t()}catch(e){return!0}}},"0GbY":function(t,e,n){var r=n("Qo9l"),o=n("2oRo"),i=function(t){return"function"==typeof t?t:void 0};t.exports=function(t,e){return arguments.length<2?i(r[t])||i(o[t]):r[t]&&r[t][e]||o[t]&&o[t][e]}},"0eef":function(t,e,n){"use strict";var r={}.propertyIsEnumerable,o=Object.getOwnPropertyDescriptor,i=o&&!r.call({1:2},1);e.f=i?function(t){var e=o(this,t);return!!e&&e.enumerable}:r},"0rvr":function(t,e,n){var r=n("glrk"),o=n("O741");t.exports=Object.setPrototypeOf||("__proto__"in{}?function(){var t,e=!1,n={};try{(t=Object.getOwnPropertyDescriptor(Object.prototype,"__proto__").set).call(n,[]),e=n instanceof Array}catch(i){}return function(n,i){return r(n),o(i),e?t.call(n,i):n.__proto__=i,n}}():void 0)},"14Sl":function(t,e,n){"use strict";n("rB9j");var r=n("busE"),o=n("0Dky"),i=n("tiKp"),a=n("kmMV"),u=n("kRJp"),c=i("species"),s=!o((function(){var t=/./;return t.exec=function(){var t=[];return t.groups={a:"7"},t},"7"!=="".replace(t,"$")})),l="$0"==="a".replace(/./,"$0"),f=i("replace"),p=!!/./[f]&&""===/./[f]("a","$0"),h=!o((function(){var t=/(?:)/,e=t.exec;t.exec=function(){return e.apply(this,arguments)};var n="ab".split(t);return 2!==n.length||"a"!==n[0]||"b"!==n[1]}));t.exports=function(t,e,n,f){var d=i(t),v=!o((function(){var e={};return e[d]=function(){return 7},7!=""[t](e)})),g=v&&!o((function(){var e=!1,n=/a/;return"split"===t&&((n={}).constructor={},n.constructor[c]=function(){return n},n.flags="",n[d]=/./[d]),n.exec=function(){return e=!0,null},n[d](""),!e}));if(!v||!g||"replace"===t&&(!s||!l||p)||"split"===t&&!h){var m=/./[d],y=n(d,""[t],(function(t,e,n,r,o){return e.exec===a?v&&!o?{done:!0,value:m.call(e,n,r)}:{done:!0,value:t.call(n,e,r)}:{done:!1}}),{REPLACE_KEEPS_$0:l,REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE:p}),b=y[0],w=y[1];r(String.prototype,t,b),r(RegExp.prototype,d,2==e?function(t,e){return w.call(t,this,e)}:function(t){return w.call(t,this)})}f&&u(RegExp.prototype[d],"sham",!0)}},"1E5z":function(t,e,n){var r=n("m/L8").f,o=n("UTVS"),i=n("tiKp")("toStringTag");t.exports=function(t,e,n){t&&!o(t=n?t:t.prototype,i)&&r(t,i,{configurable:!0,value:e})}},"284h":function(t,e,n){var r=n("cDf5");function o(){if("function"!=typeof WeakMap)return null;var t=new WeakMap;return o=function(){return t},t}t.exports=function(t){if(t&&t.__esModule)return t;if(null===t||"object"!==r(t)&&"function"!=typeof t)return{default:t};var e=o();if(e&&e.has(t))return e.get(t);var n={},i=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var a in t)if(Object.prototype.hasOwnProperty.call(t,a)){var u=i?Object.getOwnPropertyDescriptor(t,a):null;u&&(u.get||u.set)?Object.defineProperty(n,a,u):n[a]=t[a]}return n.default=t,e&&e.set(t,n),n}},"2oRo":function(t,e,n){(function(e){var n=function(t){return t&&t.Math==Math&&t};t.exports=n("object"==typeof globalThis&&globalThis)||n("object"==typeof window&&window)||n("object"==typeof self&&self)||n("object"==typeof e&&e)||function(){return this}()||Function("return this")()}).call(this,n("yLpj"))},"30RF":function(t,e,n){"use strict";n.d(e,"d",(function(){return l})),n.d(e,"a",(function(){return f})),n.d(e,"c",(function(){return p})),n.d(e,"b",(function(){return h}));n("inlA"),n("LKBx"),n("EnZy"),n("3bBZ"),n("4mDm"),n("07d7"),n("PKPk"),n("Tskq");var r=n("LYrO"),o=n("cSJ8"),i=function(t){return void 0===t?t:"/"===t?"/":"/"===t.charAt(t.length-1)?t.slice(0,-1):t},a=new Map,u=[],c=function(t){var e=decodeURIComponent(t);return Object(o.a)(e,"").split("#")[0].split("?")[0]};function s(t){return t.startsWith("/")||t.startsWith("https://")||t.startsWith("http://")?t:new URL(t,window.location.href+(window.location.href.endsWith("/")?"":"/")).pathname}var l=function(t){u=t},f=function(t){var e=d(t),n=u.map((function(t){var e=t.path;return{path:t.matchPath,originalPath:e}})),o=Object(r.pick)(n,e);return o?i(o.route.originalPath):null},p=function(t){var e=d(t),n=u.map((function(t){var e=t.path;return{path:t.matchPath,originalPath:e}})),o=Object(r.pick)(n,e);return o?o.params:{}},h=function(t){var e=c(s(t));if(a.has(e))return a.get(e);var n=f(e);return n||(n=d(t)),a.set(e,n),n},d=function(t){var e=c(s(t));return"/index.html"===e&&(e="/"),e=i(e)}},"33Wh":function(t,e,n){var r=n("yoRg"),o=n("eDl+");t.exports=Object.keys||function(t){return r(t,o)}},"3bBZ":function(t,e,n){var r=n("2oRo"),o=n("/byt"),i=n("4mDm"),a=n("kRJp"),u=n("tiKp"),c=u("iterator"),s=u("toStringTag"),l=i.values;for(var f in o){var p=r[f],h=p&&p.prototype;if(h){if(h[c]!==l)try{a(h,c,l)}catch(v){h[c]=l}if(h[s]||a(h,s,f),o[f])for(var d in i)if(h[d]!==i[d])try{a(h,d,i[d])}catch(v){h[d]=i[d]}}}},"3uz+":function(t,e,n){"use strict";e.__esModule=!0,e.useScrollRestoration=function(t){var e=(0,i.useLocation)(),n=(0,o.useContext)(r.ScrollContext),a=(0,o.useRef)();return(0,o.useLayoutEffect)((function(){if(a.current){var r=n.read(e,t);a.current.scrollTo(0,r||0)}}),[]),{ref:a,onScroll:function(){a.current&&n.save(e,t,a.current.scrollTop)}}};var r=n("Enzk"),o=n("q1tI"),i=n("YwZP")},"4WOD":function(t,e,n){var r=n("UTVS"),o=n("ewvW"),i=n("93I0"),a=n("4Xet"),u=i("IE_PROTO"),c=Object.prototype;t.exports=a?Object.getPrototypeOf:function(t){return t=o(t),r(t,u)?t[u]:"function"==typeof t.constructor&&t instanceof t.constructor?t.constructor.prototype:t instanceof Object?c:null}},"4Xet":function(t,e,n){var r=n("0Dky");t.exports=!r((function(){function t(){}return t.prototype.constructor=null,Object.getPrototypeOf(new t)!==t.prototype}))},"4mDm":function(t,e,n){"use strict";var r=n("/GqU"),o=n("RNIs"),i=n("P4y1"),a=n("afO8"),u=n("fdAy"),c=a.set,s=a.getterFor("Array Iterator");t.exports=u(Array,"Array",(function(t,e){c(this,{type:"Array Iterator",target:r(t),index:0,kind:e})}),(function(){var t=s(this),e=t.target,n=t.kind,r=t.index++;return!e||r>=e.length?(t.target=void 0,{value:void 0,done:!0}):"keys"==n?{value:r,done:!1}:"values"==n?{value:e[r],done:!1}:{value:[r,e[r]],done:!1}}),"values"),i.Arguments=i.Array,o("keys"),o("values"),o("entries")},"4syw":function(t,e,n){var r=n("busE");t.exports=function(t,e,n){for(var o in e)r(t,o,e[o],n);return t}},"568m":function(t,e,n){},"5mdu":function(t,e){t.exports=function(t){try{return{error:!1,value:t()}}catch(e){return{error:!0,value:e}}}},"5s+n":function(t,e,n){"use strict";var r,o,i,a,u=n("I+eb"),c=n("xDBR"),s=n("2oRo"),l=n("0GbY"),f=n("/qmn"),p=n("busE"),h=n("4syw"),d=n("1E5z"),v=n("JiZb"),g=n("hh1v"),m=n("HAuM"),y=n("GarU"),b=n("iSVu"),w=n("ImZN"),x=n("HH4o"),S=n("SEBh"),R=n("LPSS").set,E=n("tXUg"),O=n("zfnd"),P=n("RN6c"),j=n("8GlL"),k=n("5mdu"),_=n("afO8"),T=n("lMq5"),C=n("tiKp"),I=n("YF1G"),L=n("LQDL"),A=C("species"),D="Promise",M=_.get,N=_.set,U=_.getterFor(D),W=f,F=s.TypeError,q=s.document,Y=s.process,B=l("fetch"),G=j.f,V=G,K=!!(q&&q.createEvent&&s.dispatchEvent),H="function"==typeof PromiseRejectionEvent,J=T(D,(function(){if(!(b(W)!==String(W))){if(66===L)return!0;if(!I&&!H)return!0}if(c&&!W.prototype.finally)return!0;if(L>=51&&/native code/.test(W))return!1;var t=W.resolve(1),e=function(t){t((function(){}),(function(){}))};return(t.constructor={})[A]=e,!(t.then((function(){}))instanceof e)})),z=J||!x((function(t){W.all(t).catch((function(){}))})),Q=function(t){var e;return!(!g(t)||"function"!=typeof(e=t.then))&&e},Z=function(t,e){if(!t.notified){t.notified=!0;var n=t.reactions;E((function(){for(var r=t.value,o=1==t.state,i=0;n.length>i;){var a,u,c,s=n[i++],l=o?s.ok:s.fail,f=s.resolve,p=s.reject,h=s.domain;try{l?(o||(2===t.rejection&&et(t),t.rejection=1),!0===l?a=r:(h&&h.enter(),a=l(r),h&&(h.exit(),c=!0)),a===s.promise?p(F("Promise-chain cycle")):(u=Q(a))?u.call(a,f,p):f(a)):p(r)}catch(d){h&&!c&&h.exit(),p(d)}}t.reactions=[],t.notified=!1,e&&!t.rejection&&$(t)}))}},X=function(t,e,n){var r,o;K?((r=q.createEvent("Event")).promise=e,r.reason=n,r.initEvent(t,!1,!0),s.dispatchEvent(r)):r={promise:e,reason:n},!H&&(o=s["on"+t])?o(r):"unhandledrejection"===t&&P("Unhandled promise rejection",n)},$=function(t){R.call(s,(function(){var e,n=t.facade,r=t.value;if(tt(t)&&(e=k((function(){I?Y.emit("unhandledRejection",r,n):X("unhandledrejection",n,r)})),t.rejection=I||tt(t)?2:1,e.error))throw e.value}))},tt=function(t){return 1!==t.rejection&&!t.parent},et=function(t){R.call(s,(function(){var e=t.facade;I?Y.emit("rejectionHandled",e):X("rejectionhandled",e,t.value)}))},nt=function(t,e,n){return function(r){t(e,r,n)}},rt=function(t,e,n){t.done||(t.done=!0,n&&(t=n),t.value=e,t.state=2,Z(t,!0))},ot=function(t,e,n){if(!t.done){t.done=!0,n&&(t=n);try{if(t.facade===e)throw F("Promise can't be resolved itself");var r=Q(e);r?E((function(){var n={done:!1};try{r.call(e,nt(ot,n,t),nt(rt,n,t))}catch(o){rt(n,o,t)}})):(t.value=e,t.state=1,Z(t,!1))}catch(o){rt({done:!1},o,t)}}};J&&(W=function(t){y(this,W,D),m(t),r.call(this);var e=M(this);try{t(nt(ot,e),nt(rt,e))}catch(n){rt(e,n)}},(r=function(t){N(this,{type:D,done:!1,notified:!1,parent:!1,reactions:[],rejection:!1,state:0,value:void 0})}).prototype=h(W.prototype,{then:function(t,e){var n=U(this),r=G(S(this,W));return r.ok="function"!=typeof t||t,r.fail="function"==typeof e&&e,r.domain=I?Y.domain:void 0,n.parent=!0,n.reactions.push(r),0!=n.state&&Z(n,!1),r.promise},catch:function(t){return this.then(void 0,t)}}),o=function(){var t=new r,e=M(t);this.promise=t,this.resolve=nt(ot,e),this.reject=nt(rt,e)},j.f=G=function(t){return t===W||t===i?new o(t):V(t)},c||"function"!=typeof f||(a=f.prototype.then,p(f.prototype,"then",(function(t,e){var n=this;return new W((function(t,e){a.call(n,t,e)})).then(t,e)}),{unsafe:!0}),"function"==typeof B&&u({global:!0,enumerable:!0,forced:!0},{fetch:function(t){return O(W,B.apply(s,arguments))}}))),u({global:!0,wrap:!0,forced:J},{Promise:W}),d(W,D,!1,!0),v(D),i=l(D),u({target:D,stat:!0,forced:J},{reject:function(t){var e=G(this);return e.reject.call(void 0,t),e.promise}}),u({target:D,stat:!0,forced:c||J},{resolve:function(t){return O(c&&this===i?W:this,t)}}),u({target:D,stat:!0,forced:z},{all:function(t){var e=this,n=G(e),r=n.resolve,o=n.reject,i=k((function(){var n=m(e.resolve),i=[],a=0,u=1;w(t,(function(t){var c=a++,s=!1;i.push(void 0),u++,n.call(e,t).then((function(t){s||(s=!0,i[c]=t,--u||r(i))}),o)})),--u||r(i)}));return i.error&&o(i.value),n.promise},race:function(t){var e=this,n=G(e),r=n.reject,o=k((function(){var o=m(e.resolve);w(t,(function(t){o.call(e,t).then(n.resolve,r)}))}));return o.error&&r(o.value),n.promise}})},"5yr3":function(t,e,n){"use strict";var r=function(t){return t=t||Object.create(null),{on:function(e,n){(t[e]||(t[e]=[])).push(n)},off:function(e,n){t[e]&&t[e].splice(t[e].indexOf(n)>>>0,1)},emit:function(e,n){(t[e]||[]).slice().map((function(t){t(n)})),(t["*"]||[]).slice().map((function(t){t(e,n)}))}}}();e.a=r},"6JNq":function(t,e,n){var r=n("UTVS"),o=n("Vu81"),i=n("Bs8V"),a=n("m/L8");t.exports=function(t,e){for(var n=o(e),u=a.f,c=i.f,s=0;s=0||(o[n]=t[n]);return o}},"8YOa":function(t,e,n){var r=n("0BK2"),o=n("hh1v"),i=n("UTVS"),a=n("m/L8").f,u=n("kOOl"),c=n("uy83"),s=u("meta"),l=0,f=Object.isExtensible||function(){return!0},p=function(t){a(t,s,{value:{objectID:"O"+ ++l,weakData:{}}})},h=t.exports={REQUIRED:!1,fastKey:function(t,e){if(!o(t))return"symbol"==typeof t?t:("string"==typeof t?"S":"P")+t;if(!i(t,s)){if(!f(t))return"F";if(!e)return"E";p(t)}return t[s].objectID},getWeakData:function(t,e){if(!i(t,s)){if(!f(t))return!0;if(!e)return!1;p(t)}return t[s].weakData},onFreeze:function(t){return c&&h.REQUIRED&&f(t)&&!i(t,s)&&p(t),t}};r[s]=!0},"93I0":function(t,e,n){var r=n("VpIT"),o=n("kOOl"),i=r("keys");t.exports=function(t){return i[t]||(i[t]=o(t))}},"94VI":function(t,e){e.polyfill=function(t){return t}},"9Xx/":function(t,e,n){"use strict";n.d(e,"c",(function(){return c})),n.d(e,"d",(function(){return s})),n.d(e,"a",(function(){return i})),n.d(e,"b",(function(){return a}));var r=Object.assign||function(t){for(var e=1;e1&&void 0!==arguments[1]?arguments[1]:{},s=c.state,l=c.replace,f=void 0!==l&&l;if("number"==typeof e)t.history.go(e);else{s=r({},s,{key:Date.now()+""});try{a||f?t.history.replaceState(s,null,e):t.history.pushState(s,null,e)}catch(h){t.location[f?"replace":"assign"](e)}}i=o(t),a=!0;var p=new Promise((function(t){return u=t}));return n.forEach((function(t){return t({location:i,action:"PUSH"})})),p}}},a=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"/",e=t.indexOf("?"),n={pathname:e>-1?t.substr(0,e):t,search:e>-1?t.substr(e):""},r=0,o=[n],i=[null];return{get location(){return o[r]},addEventListener:function(t,e){},removeEventListener:function(t,e){},history:{get entries(){return o},get index(){return r},get state(){return i[r]},pushState:function(t,e,n){var a=n.split("?"),u=a[0],c=a[1],s=void 0===c?"":c;r++,o.push({pathname:u,search:s.length?"?"+s:s}),i.push(t)},replaceState:function(t,e,n){var a=n.split("?"),u=a[0],c=a[1],s=void 0===c?"":c;o[r]={pathname:u,search:s},i[r]=t},go:function(t){var e=r+t;e<0||e>i.length-1||(r=e)}}}},u=!("undefined"==typeof window||!window.document||!window.document.createElement),c=i(u?window:a()),s=c.navigate},"9d/t":function(t,e,n){var r=n("AO7/"),o=n("xrYK"),i=n("tiKp")("toStringTag"),a="Arguments"==o(function(){return arguments}());t.exports=r?o:function(t){var e,n,r;return void 0===t?"Undefined":null===t?"Null":"string"==typeof(n=function(t,e){try{return t[e]}catch(n){}}(e=Object(t),i))?n:a?o(e):"Object"==(r=o(e))&&"function"==typeof e.callee?"Arguments":r}},"9hXx":function(t,e,n){"use strict";e.__esModule=!0,e.default=void 0;e.default=function(t,e){if(!Array.isArray(e))return"manifest.webmanifest";var n=e.find((function(e){return t.startsWith(e.start_url)}));return n?"manifest_"+n.lang+".webmanifest":"manifest.webmanifest"}},A2ZE:function(t,e,n){var r=n("HAuM");t.exports=function(t,e,n){if(r(t),void 0===e)return t;switch(n){case 0:return function(){return t.call(e)};case 1:return function(n){return t.call(e,n)};case 2:return function(n,r){return t.call(e,n,r)};case 3:return function(n,r,o){return t.call(e,n,r,o)}}return function(){return t.apply(e,arguments)}}},"AO7/":function(t,e,n){var r={};r[n("tiKp")("toStringTag")]="z",t.exports="[object z]"===String(r)},Bs8V:function(t,e,n){var r=n("g6v/"),o=n("0eef"),i=n("XGwC"),a=n("/GqU"),u=n("wE6v"),c=n("UTVS"),s=n("DPsx"),l=Object.getOwnPropertyDescriptor;e.f=r?l:function(t,e){if(t=a(t),e=u(e,!0),s)try{return l(t,e)}catch(n){}if(c(t,e))return i(!o.f.call(t,e),t[e])}},DPsx:function(t,e,n){var r=n("g6v/"),o=n("0Dky"),i=n("zBJ4");t.exports=!r&&!o((function(){return 7!=Object.defineProperty(i("div"),"a",{get:function(){return 7}}).a}))},EnZy:function(t,e,n){"use strict";var r=n("14Sl"),o=n("ROdP"),i=n("glrk"),a=n("HYAF"),u=n("SEBh"),c=n("iqWW"),s=n("UMSQ"),l=n("FMNM"),f=n("kmMV"),p=n("0Dky"),h=[].push,d=Math.min,v=!p((function(){return!RegExp(4294967295,"y")}));r("split",2,(function(t,e,n){var r;return r="c"=="abbc".split(/(b)*/)[1]||4!="test".split(/(?:)/,-1).length||2!="ab".split(/(?:ab)*/).length||4!=".".split(/(.?)(.?)/).length||".".split(/()()/).length>1||"".split(/.?/).length?function(t,n){var r=String(a(this)),i=void 0===n?4294967295:n>>>0;if(0===i)return[];if(void 0===t)return[r];if(!o(t))return e.call(r,t,i);for(var u,c,s,l=[],p=(t.ignoreCase?"i":"")+(t.multiline?"m":"")+(t.unicode?"u":"")+(t.sticky?"y":""),d=0,v=new RegExp(t.source,p+"g");(u=f.call(v,r))&&!((c=v.lastIndex)>d&&(l.push(r.slice(d,u.index)),u.length>1&&u.index=i));)v.lastIndex===u.index&&v.lastIndex++;return d===r.length?!s&&v.test("")||l.push(""):l.push(r.slice(d)),l.length>i?l.slice(0,i):l}:"0".split(void 0,0).length?function(t,n){return void 0===t&&0===n?[]:e.call(this,t,n)}:e,[function(e,n){var o=a(this),i=null==e?void 0:e[t];return void 0!==i?i.call(e,o,n):r.call(String(o),e,n)},function(t,o){var a=n(r,t,this,o,r!==e);if(a.done)return a.value;var f=i(t),p=String(this),h=u(f,RegExp),g=f.unicode,m=(f.ignoreCase?"i":"")+(f.multiline?"m":"")+(f.unicode?"u":"")+(v?"y":"g"),y=new h(v?f:"^(?:"+f.source+")",m),b=void 0===o?4294967295:o>>>0;if(0===b)return[];if(0===p.length)return null===l(y,p)?[p]:[];for(var w=0,x=0,S=[];xp;p++)if((d=R(t[p]))&&d instanceof s)return d;return new s(!1)}l=f.call(t)}for(v=l.next;!(g=v.call(l)).done;){try{d=R(g.value)}catch(E){throw c(l),E}if("object"==typeof d&&d&&d instanceof s)return d}return new s(!1)}},JBy8:function(t,e,n){var r=n("yoRg"),o=n("eDl+").concat("length","prototype");e.f=Object.getOwnPropertyNames||function(t){return r(t,o)}},JTJg:function(t,e,n){"use strict";var r=n("I+eb"),o=n("WjRb"),i=n("HYAF");r({target:"String",proto:!0,forced:!n("qxPZ")("includes")},{includes:function(t){return!!~String(i(this)).indexOf(o(t),arguments.length>1?arguments[1]:void 0)}})},JeVI:function(t){t.exports=JSON.parse("[]")},JiZb:function(t,e,n){"use strict";var r=n("0GbY"),o=n("m/L8"),i=n("tiKp"),a=n("g6v/"),u=i("species");t.exports=function(t){var e=r(t),n=o.f;a&&e&&!e[u]&&n(e,u,{configurable:!0,get:function(){return this}})}},KmKo:function(t,e,n){var r=n("glrk");t.exports=function(t){var e=t.return;if(void 0!==e)return r(e.call(t)).value}},LKBx:function(t,e,n){"use strict";var r,o=n("I+eb"),i=n("Bs8V").f,a=n("UMSQ"),u=n("WjRb"),c=n("HYAF"),s=n("qxPZ"),l=n("xDBR"),f="".startsWith,p=Math.min,h=s("startsWith");o({target:"String",proto:!0,forced:!!(l||h||(r=i(String.prototype,"startsWith"),!r||r.writable))&&!h},{startsWith:function(t){var e=String(c(this));u(t);var n=a(p(arguments.length>1?arguments[1]:void 0,e.length)),r=String(t);return f?f.call(e,r,n):e.slice(n,n+r.length)===r}})},LPSS:function(t,e,n){var r,o,i,a=n("2oRo"),u=n("0Dky"),c=n("A2ZE"),s=n("G+Rx"),l=n("zBJ4"),f=n("HNyW"),p=n("YF1G"),h=a.location,d=a.setImmediate,v=a.clearImmediate,g=a.process,m=a.MessageChannel,y=a.Dispatch,b=0,w={},x=function(t){if(w.hasOwnProperty(t)){var e=w[t];delete w[t],e()}},S=function(t){return function(){x(t)}},R=function(t){x(t.data)},E=function(t){a.postMessage(t+"",h.protocol+"//"+h.host)};d&&v||(d=function(t){for(var e=[],n=1;arguments.length>n;)e.push(arguments[n++]);return w[++b]=function(){("function"==typeof t?t:Function(t)).apply(void 0,e)},r(b),b},v=function(t){delete w[t]},p?r=function(t){g.nextTick(S(t))}:y&&y.now?r=function(t){y.now(S(t))}:m&&!f?(i=(o=new m).port2,o.port1.onmessage=R,r=c(i.postMessage,i,1)):a.addEventListener&&"function"==typeof postMessage&&!a.importScripts&&h&&"file:"!==h.protocol&&!u(E)?(r=E,a.addEventListener("message",R,!1)):r="onreadystatechange"in l("script")?function(t){s.appendChild(l("script")).onreadystatechange=function(){s.removeChild(this),x(t)}}:function(t){setTimeout(S(t),0)}),t.exports={set:d,clear:v}},LQDL:function(t,e,n){var r,o,i=n("2oRo"),a=n("NC/Y"),u=i.process,c=u&&u.versions,s=c&&c.v8;s?o=(r=s.split("."))[0]+r[1]:a&&(!(r=a.match(/Edge\/(\d+)/))||r[1]>=74)&&(r=a.match(/Chrome\/(\d+)/))&&(o=r[1]),t.exports=o&&+o},LYrO:function(t,e,n){"use strict";n.r(e),n.d(e,"startsWith",(function(){return i})),n.d(e,"pick",(function(){return a})),n.d(e,"match",(function(){return u})),n.d(e,"resolve",(function(){return c})),n.d(e,"insertParams",(function(){return s})),n.d(e,"validateRedirect",(function(){return l})),n.d(e,"shallowCompare",(function(){return b}));var r=n("QLaP"),o=n.n(r),i=function(t,e){return t.substr(0,e.length)===e},a=function(t,e){for(var n=void 0,r=void 0,i=e.split("?")[0],a=g(i),u=""===a[0],c=v(t),s=0,l=c.length;se.score?-1:t.index-e.index}))},g=function(t){return t.replace(/(^\/+|\/+$)/g,"").split("/")},m=function(t){for(var e=arguments.length,n=Array(e>1?e-1:0),r=1;r0})))&&n.length>0?"?"+n.join("&"):"")},y=["uri","path"],b=function(t,e){var n=Object.keys(t);return n.length===Object.keys(e).length&&n.every((function(n){return e.hasOwnProperty(n)&&t[n]===e[n]}))}},LeKB:function(t,e,n){t.exports=[{plugin:n("q9nr"),options:{plugins:[],maxWidth:800,quality:100,linkImagesToOriginal:!0,showCaptions:!1,markdownCaptions:!1,sizeByPixelDensity:!1,backgroundColor:"white",withWebp:!1,tracedSVG:!1,loading:"lazy",disableBgImageOnAlpha:!1,disableBgImage:!1}},{plugin:n("npZl"),options:{plugins:[],name:"gatsby-starter-hello-friend",short_name:"맨땅에 코딩",start_url:"/",background_color:"#292a2d",theme_color:"#292a2d",display:"minimal-ui",icon:"src/images/logo.png",legacy:!0,theme_color_in_head:!0,cache_busting_mode:"query",crossOrigin:"anonymous",include_favicon:!0,cacheDigest:"0f3184a956c4660a6190654597374554"}},{plugin:n("GddB"),options:{plugins:[]}}]},MMVs:function(t,e,n){t.exports=function(){var t=!1;-1!==navigator.appVersion.indexOf("MSIE 10")&&(t=!0);var e,n=[],r="object"==typeof document&&document,o=t?r.documentElement.doScroll("left"):r.documentElement.doScroll,i=r&&(o?/^loaded|^c/:/^loaded|^i|^c/).test(r.readyState);return!i&&r&&r.addEventListener("DOMContentLoaded",e=function(){for(r.removeEventListener("DOMContentLoaded",e),i=1;e=n.shift();)e()}),function(t){i?setTimeout(t,0):n.push(t)}}()},"N+g0":function(t,e,n){var r=n("g6v/"),o=n("m/L8"),i=n("glrk"),a=n("33Wh");t.exports=r?Object.defineProperties:function(t,e){i(t);for(var n,r=a(e),u=r.length,c=0;u>c;)o.f(t,n=r[c++],e[n]);return t}},"NC/Y":function(t,e,n){var r=n("0GbY");t.exports=r("navigator","userAgent")||""},NSX3:function(t,e,n){"use strict";n.r(e);var r=n("xtsi");"https:"!==window.location.protocol&&"localhost"!==window.location.hostname?console.error("Service workers can only be used over HTTPS, or on localhost for development"):"serviceWorker"in navigator&&navigator.serviceWorker.register("/sw.js").then((function(t){t.addEventListener("updatefound",(function(){Object(r.apiRunner)("onServiceWorkerUpdateFound",{serviceWorker:t});var e=t.installing;console.log("installingWorker",e),e.addEventListener("statechange",(function(){switch(e.state){case"installed":navigator.serviceWorker.controller?(window.___swUpdated=!0,Object(r.apiRunner)("onServiceWorkerUpdateReady",{serviceWorker:t}),window.___failedResources&&(console.log("resources failed, SW updated - reloading"),window.location.reload())):(console.log("Content is now available offline!"),Object(r.apiRunner)("onServiceWorkerInstalled",{serviceWorker:t}));break;case"redundant":console.error("The installing service worker became redundant."),Object(r.apiRunner)("onServiceWorkerRedundant",{serviceWorker:t});break;case"activated":Object(r.apiRunner)("onServiceWorkerActive",{serviceWorker:t})}}))}))})).catch((function(t){console.error("Error during service worker registration:",t)}))},NaFW:function(t,e,n){var r=n("9d/t"),o=n("P4y1"),i=n("tiKp")("iterator");t.exports=function(t){if(null!=t)return t[i]||t["@@iterator"]||o[r(t)]}},NsGk:function(t,e,n){e.components={"component---src-pages-404-js":function(){return Promise.all([n.e(0),n.e(1),n.e(5)]).then(n.bind(null,"w2l6"))},"component---src-templates-index-js":function(){return Promise.all([n.e(0),n.e(1),n.e(2),n.e(6)]).then(n.bind(null,"TRom"))},"component---src-templates-page-js":function(){return Promise.all([n.e(0),n.e(1),n.e(2),n.e(7)]).then(n.bind(null,"sweJ"))},"component---src-templates-tags-js":function(){return Promise.all([n.e(0),n.e(1),n.e(2),n.e(8)]).then(n.bind(null,"MN1z"))}}},O741:function(t,e,n){var r=n("hh1v");t.exports=function(t){if(!r(t)&&null!==t)throw TypeError("Can't set "+String(t)+" as a prototype");return t}},P4y1:function(t,e){t.exports={}},PJYZ:function(t,e){t.exports=function(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}},PKPk:function(t,e,n){"use strict";var r=n("ZUd8").charAt,o=n("afO8"),i=n("fdAy"),a=o.set,u=o.getterFor("String Iterator");i(String,"String",(function(t){a(this,{type:"String Iterator",string:String(t),index:0})}),(function(){var t,e=u(this),n=e.string,o=e.index;return o>=n.length?{value:void 0,done:!0}:(t=r(n,o),e.index+=t.length,{value:t,done:!1})}))},QLaP:function(t,e,n){"use strict";t.exports=function(t,e,n,r,o,i,a,u){if(!t){var c;if(void 0===e)c=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var s=[n,r,o,i,a,u],l=0;(c=new Error(e.replace(/%s/g,(function(){return s[l++]})))).name="Invariant Violation"}throw c.framesToPop=1,c}}},Qo9l:function(t,e,n){var r=n("2oRo");t.exports=r},RK3t:function(t,e,n){var r=n("0Dky"),o=n("xrYK"),i="".split;t.exports=r((function(){return!Object("z").propertyIsEnumerable(0)}))?function(t){return"String"==o(t)?i.call(t,""):Object(t)}:Object},RN6c:function(t,e,n){var r=n("2oRo");t.exports=function(t,e){var n=r.console;n&&n.error&&(1===arguments.length?n.error(t):n.error(t,e))}},RNIs:function(t,e,n){var r=n("tiKp"),o=n("fHMY"),i=n("m/L8"),a=r("unscopables"),u=Array.prototype;null==u[a]&&i.f(u,a,{configurable:!0,value:o(null)}),t.exports=function(t){u[a][t]=!0}},ROdP:function(t,e,n){var r=n("hh1v"),o=n("xrYK"),i=n("tiKp")("match");t.exports=function(t){var e;return r(t)&&(void 0!==(e=t[i])?!!e:"RegExp"==o(t))}},Rm1S:function(t,e,n){"use strict";var r=n("14Sl"),o=n("glrk"),i=n("UMSQ"),a=n("HYAF"),u=n("iqWW"),c=n("FMNM");r("match",1,(function(t,e,n){return[function(e){var n=a(this),r=null==e?void 0:e[t];return void 0!==r?r.call(e,n):new RegExp(e)[t](String(n))},function(t){var r=n(e,t,this);if(r.done)return r.value;var a=o(t),s=String(this);if(!a.global)return c(a,s);var l=a.unicode;a.lastIndex=0;for(var f,p=[],h=0;null!==(f=c(a,s));){var d=String(f[0]);p[h]=d,""===d&&(a.lastIndex=u(s,i(a.lastIndex),l)),h++}return 0===h?null:p}]}))},SEBh:function(t,e,n){var r=n("glrk"),o=n("HAuM"),i=n("tiKp")("species");t.exports=function(t,e){var n,a=r(t).constructor;return void 0===a||null==(n=r(a)[i])?e:o(n)}},STAE:function(t,e,n){var r=n("0Dky");t.exports=!!Object.getOwnPropertySymbols&&!r((function(){return!String(Symbol())}))},TWQb:function(t,e,n){var r=n("/GqU"),o=n("UMSQ"),i=n("I8vh"),a=function(t){return function(e,n,a){var u,c=r(e),s=o(c.length),l=i(a,s);if(t&&n!=n){for(;s>l;)if((u=c[l++])!=u)return!0}else for(;s>l;l++)if((t||l in c)&&c[l]===n)return t||l||0;return!t&&-1}};t.exports={includes:a(!0),indexOf:a(!1)}},TqRt:function(t,e){t.exports=function(t){return t&&t.__esModule?t:{default:t}}},Tskq:function(t,e,n){"use strict";var r=n("bWFh"),o=n("ZWaQ");t.exports=r("Map",(function(t){return function(){return t(this,arguments.length?arguments[0]:void 0)}}),o)},UMSQ:function(t,e,n){var r=n("ppGB"),o=Math.min;t.exports=function(t){return t>0?o(r(t),9007199254740991):0}},UTVS:function(t,e){var n={}.hasOwnProperty;t.exports=function(t,e){return n.call(t,e)}},UxWs:function(t,e,n){"use strict";n.r(e);n("hByQ"),n("Rm1S"),n("zKZe");var r=n("VbXa"),o=n.n(r),i=n("xtsi"),a=n("q1tI"),u=n.n(a),c=n("i8i4"),s=n.n(c),l=n("YwZP"),f=n("7hJ6"),p=n("MMVs"),h=n.n(p),d=n("Wbzz"),v=n("17x9"),g=n.n(v),m=n("emEt"),y=n("YLt+"),b=n("5yr3"),w={id:"gatsby-announcer",style:{position:"absolute",top:0,width:1,height:1,padding:0,overflow:"hidden",clip:"rect(0, 0, 0, 0)",whiteSpace:"nowrap",border:0},"aria-live":"assertive","aria-atomic":"true"},x=n("9Xx/"),S=n("+ZDr"),R=y.reduce((function(t,e){return t[e.fromPath]=e,t}),{});function E(t){var e=R[t];return null!=e&&(window.___replace(e.toPath),!0)}var O=function(t,e){E(t.pathname)||Object(i.apiRunner)("onPreRouteUpdate",{location:t,prevLocation:e})},P=function(t,e){E(t.pathname)||Object(i.apiRunner)("onRouteUpdate",{location:t,prevLocation:e})},j=function(t,e){if(void 0===e&&(e={}),"number"!=typeof t){var n=Object(S.parsePath)(t).pathname,r=R[n];if(r&&(t=r.toPath,n=Object(S.parsePath)(t).pathname),window.___swUpdated)window.location=n;else{var o=setTimeout((function(){b.a.emit("onDelayedLoadPageResources",{pathname:n}),Object(i.apiRunner)("onRouteUpdateDelayed",{location:window.location})}),1e3);m.default.loadPage(n).then((function(r){if(!r||r.status===m.PageResourceStatus.Error)return window.history.replaceState({},"",location.href),window.location=n,void clearTimeout(o);r&&r.page.webpackCompilationHash!==window.___webpackCompilationHash&&("serviceWorker"in navigator&&null!==navigator.serviceWorker.controller&&"activated"===navigator.serviceWorker.controller.state&&navigator.serviceWorker.controller.postMessage({gatsbyApi:"clearPathResources"}),window.location=n),Object(l.navigate)(t,e),clearTimeout(o)}))}}else x.c.navigate(t)};function k(t,e){var n=this,r=e.location,o=r.pathname,a=r.hash,u=Object(i.apiRunner)("shouldUpdateScroll",{prevRouterProps:t,pathname:o,routerProps:{location:r},getSavedScrollPosition:function(t){return n._stateStorage.read(t)}});if(u.length>0)return u[u.length-1];if(t&&t.location.pathname===o)return a?decodeURI(a.slice(1)):[0,0];return!0}var _=function(t){function e(e){var n;return(n=t.call(this,e)||this).announcementRef=u.a.createRef(),n}o()(e,t);var n=e.prototype;return n.componentDidUpdate=function(t,e){var n=this;requestAnimationFrame((function(){var t="new page at "+n.props.location.pathname;document.title&&(t=document.title);var e=document.querySelectorAll("#gatsby-focus-wrapper h1");e&&e.length&&(t=e[0].textContent);var r="Navigated to "+t;n.announcementRef.current&&(n.announcementRef.current.innerText!==r&&(n.announcementRef.current.innerText=r))}))},n.render=function(){return u.a.createElement("div",Object.assign({},w,{ref:this.announcementRef}))},e}(u.a.Component),T=function(t){function e(e){var n;return n=t.call(this,e)||this,O(e.location,null),n}o()(e,t);var n=e.prototype;return n.componentDidMount=function(){P(this.props.location,null)},n.shouldComponentUpdate=function(t){return this.props.location.href!==t.location.href&&(O(this.props.location,t.location),!0)},n.componentDidUpdate=function(t){this.props.location.href!==t.location.href&&P(this.props.location,t.location)},n.render=function(){return u.a.createElement(u.a.Fragment,null,this.props.children,u.a.createElement(_,{location:location}))},e}(u.a.Component);T.propTypes={location:g.a.object.isRequired};var C=n("IOVJ"),I=n("NsGk"),L=n.n(I);function A(t,e){for(var n in t)if(!(n in e))return!0;for(var r in e)if(t[r]!==e[r])return!0;return!1}var D=function(t){function e(e){var n;n=t.call(this)||this;var r=e.location,o=e.pageResources;return n.state={location:Object.assign({},r),pageResources:o||m.default.loadPageSync(r.pathname)},n}o()(e,t),e.getDerivedStateFromProps=function(t,e){var n=t.location;return e.location.href!==n.href?{pageResources:m.default.loadPageSync(n.pathname),location:Object.assign({},n)}:{location:Object.assign({},n)}};var n=e.prototype;return n.loadResources=function(t){var e=this;m.default.loadPage(t).then((function(n){n&&n.status!==m.PageResourceStatus.Error?e.setState({location:Object.assign({},window.location),pageResources:n}):(window.history.replaceState({},"",location.href),window.location=t)}))},n.shouldComponentUpdate=function(t,e){return e.pageResources?this.state.pageResources!==e.pageResources||(this.state.pageResources.component!==e.pageResources.component||(this.state.pageResources.json!==e.pageResources.json||(!(this.state.location.key===e.location.key||!e.pageResources.page||!e.pageResources.page.matchPath&&!e.pageResources.page.path)||function(t,e,n){return A(t.props,e)||A(t.state,n)}(this,t,e)))):(this.loadResources(t.location.pathname),!1)},n.render=function(){return this.props.children(this.state)},e}(u.a.Component),M=n("cSJ8"),N=n("JeVI"),U=new m.ProdLoader(L.a,N);Object(m.setLoader)(U),U.setApiRunner(i.apiRunner),window.asyncRequires=L.a,window.___emitter=b.a,window.___loader=m.publicLoader,x.c.listen((function(t){t.location.action=t.action})),window.___push=function(t){return j(t,{replace:!1})},window.___replace=function(t){return j(t,{replace:!0})},window.___navigate=function(t,e){return j(t,e)},E(window.location.pathname),Object(i.apiRunnerAsync)("onClientEntry").then((function(){Object(i.apiRunner)("registerServiceWorker").length>0&&n("NSX3");var t=function(t){return u.a.createElement(l.BaseContext.Provider,{value:{baseuri:"/",basepath:"/"}},u.a.createElement(C.a,t))},e=u.a.createContext({}),r=function(t){function n(){return t.apply(this,arguments)||this}return o()(n,t),n.prototype.render=function(){var t=this.props.children;return u.a.createElement(l.Location,null,(function(n){var r=n.location;return u.a.createElement(D,{location:r},(function(n){var r=n.pageResources,o=n.location,i=Object(m.getStaticQueryResults)();return u.a.createElement(d.StaticQueryContext.Provider,{value:i},u.a.createElement(e.Provider,{value:{pageResources:r,location:o}},t))}))}))},n}(u.a.Component),a=function(n){function r(){return n.apply(this,arguments)||this}return o()(r,n),r.prototype.render=function(){var n=this;return u.a.createElement(e.Consumer,null,(function(e){var r=e.pageResources,o=e.location;return u.a.createElement(T,{location:o},u.a.createElement(f.ScrollContext,{location:o,shouldUpdateScroll:k},u.a.createElement(l.Router,{basepath:"",location:o,id:"gatsby-focus-wrapper"},u.a.createElement(t,Object.assign({path:"/404.html"===r.page.path?Object(M.a)(o.pathname,""):encodeURI(r.page.matchPath||r.page.path)},n.props,{location:o,pageResources:r},r.json)))))}))},r}(u.a.Component),c=window,p=c.pagePath,v=c.location;p&&""+p!==v.pathname&&!(U.findMatchPath(Object(M.a)(v.pathname,""))||"/404.html"===p||p.match(/^\/404\/?$/)||p.match(/^\/offline-plugin-app-shell-fallback\/?$/))&&Object(l.navigate)(""+p+v.search+v.hash,{replace:!0}),m.publicLoader.loadPage(v.pathname).then((function(t){if(!t||t.status===m.PageResourceStatus.Error)throw new Error("page resources for "+v.pathname+" not found. Not rendering React");window.___webpackCompilationHash=t.page.webpackCompilationHash;var e=Object(i.apiRunner)("wrapRootElement",{element:u.a.createElement(a,null)},u.a.createElement(a,null),(function(t){return{element:t.result}})).pop(),n=function(){return u.a.createElement(r,null,e)},o=Object(i.apiRunner)("replaceHydrateFunction",void 0,s.a.hydrate)[0];h()((function(){o(u.a.createElement(n,null),"undefined"!=typeof window?document.getElementById("___gatsby"):void 0,(function(){Object(i.apiRunner)("onInitialClientRender")}))}))}))}))},VbXa:function(t,e){t.exports=function(t,e){t.prototype=Object.create(e.prototype),t.prototype.constructor=t,t.__proto__=e}},VpIT:function(t,e,n){var r=n("xDBR"),o=n("xs3f");(t.exports=function(t,e){return o[t]||(o[t]=void 0!==e?e:{})})("versions",[]).push({version:"3.7.0",mode:r?"pure":"global",copyright:"© 2020 Denis Pushkarev (zloirock.ru)"})},Vu81:function(t,e,n){var r=n("0GbY"),o=n("JBy8"),i=n("dBg+"),a=n("glrk");t.exports=r("Reflect","ownKeys")||function(t){var e=o.f(a(t)),n=i.f;return n?e.concat(n(t)):e}},WJkJ:function(t,e){t.exports="\t\n\v\f\r                 \u2028\u2029\ufeff"},WKiH:function(t,e,n){var r=n("HYAF"),o="["+n("WJkJ")+"]",i=RegExp("^"+o+o+"*"),a=RegExp(o+o+"*$"),u=function(t){return function(e){var n=String(r(e));return 1&t&&(n=n.replace(i,"")),2&t&&(n=n.replace(a,"")),n}};t.exports={start:u(1),end:u(2),trim:u(3)}},Wbzz:function(t,e,n){"use strict";n.r(e),n.d(e,"graphql",(function(){return y})),n.d(e,"StaticQueryContext",(function(){return d})),n.d(e,"StaticQuery",(function(){return g})),n.d(e,"useStaticQuery",(function(){return m})),n.d(e,"prefetchPathname",(function(){return h}));n("qePV"),n("zKZe");var r=n("q1tI"),o=n.n(r),i=n("17x9"),a=n.n(i),u=n("+ZDr"),c=n.n(u);n.d(e,"Link",(function(){return c.a})),n.d(e,"withAssetPrefix",(function(){return u.withAssetPrefix})),n.d(e,"withPrefix",(function(){return u.withPrefix})),n.d(e,"parsePath",(function(){return u.parsePath})),n.d(e,"navigate",(function(){return u.navigate})),n.d(e,"push",(function(){return u.push})),n.d(e,"replace",(function(){return u.replace})),n.d(e,"navigateTo",(function(){return u.navigateTo}));var s=n("7hJ6");n.d(e,"useScrollRestoration",(function(){return s.useScrollRestoration}));var l=n("lw3w"),f=n.n(l);n.d(e,"PageRenderer",(function(){return f.a}));var p=n("emEt"),h=p.default.enqueue,d=o.a.createContext({});function v(t){var e=t.staticQueryData,n=t.data,r=t.query,i=t.render,a=e;({}).GATSBY_EXPERIMENTAL_LAZY_DEVJS&&(a=Object.assign({},Object(p.getStaticQueryResults)(),e));var u=n?n.data:a[r]&&a[r].data;return o.a.createElement(o.a.Fragment,null,u&&i(u),!u&&o.a.createElement("div",null,"Loading (StaticQuery)"))}var g=function(t){var e=t.data,n=t.query,r=t.render,i=t.children;return o.a.createElement(d.Consumer,null,(function(t){return o.a.createElement(v,{data:e,query:n,render:r||i,staticQueryData:t})}))},m=function(t){o.a.useContext;var e=o.a.useContext(d);if(isNaN(Number(t)))throw new Error("useStaticQuery was called with a string but expects to be called using `graphql`. Try this:\n\nimport { useStaticQuery, graphql } from 'gatsby';\n\nuseStaticQuery(graphql`"+t+"`);\n");var n=!1;if({}.GATSBY_EXPERIMENTAL_LAZY_DEVJS){var r,i=Object.assign({},Object(p.getStaticQueryResults)(),e);if(null===(r=i[t])||void 0===r?void 0:r.data)return i[t].data;n=!0}else{var a;if(null===(a=e[t])||void 0===a?void 0:a.data)return e[t].data;n=!0}if(n)throw new Error("The result of this StaticQuery could not be fetched.\n\nThis is likely a bug in Gatsby and if refreshing the page does not fix it, please open an issue in https://github.com/gatsbyjs/gatsby/issues");return null};function y(){throw new Error("It appears like Gatsby is misconfigured. Gatsby related `graphql` calls are supposed to only be evaluated at compile time, and then compiled away. Unfortunately, something went wrong and the query was left in the compiled code.\n\nUnless your site has a complex or custom babel/Gatsby configuration this is likely a bug in Gatsby.")}g.propTypes={data:a.a.object,query:a.a.string.isRequired,render:a.a.func,children:a.a.func}},WjRb:function(t,e,n){var r=n("ROdP");t.exports=function(t){if(r(t))throw TypeError("The method doesn't accept regular expressions");return t}},XGwC:function(t,e){t.exports=function(t,e){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:e}}},YF1G:function(t,e,n){var r=n("xrYK"),o=n("2oRo");t.exports="process"==r(o.process)},YGK4:function(t,e,n){"use strict";var r=n("bWFh"),o=n("ZWaQ");t.exports=r("Set",(function(t){return function(){return t(this,arguments.length?arguments[0]:void 0)}}),o)},"YLt+":function(t){t.exports=JSON.parse("[]")},YNrV:function(t,e,n){"use strict";var r=n("g6v/"),o=n("0Dky"),i=n("33Wh"),a=n("dBg+"),u=n("0eef"),c=n("ewvW"),s=n("RK3t"),l=Object.assign,f=Object.defineProperty;t.exports=!l||o((function(){if(r&&1!==l({b:1},l(f({},"a",{enumerable:!0,get:function(){f(this,"b",{value:3,enumerable:!1})}}),{b:2})).b)return!0;var t={},e={},n=Symbol();return t[n]=7,"abcdefghijklmnopqrst".split("").forEach((function(t){e[t]=t})),7!=l({},t)[n]||"abcdefghijklmnopqrst"!=i(l({},e)).join("")}))?function(t,e){for(var n=c(t),o=arguments.length,l=1,f=a.f,p=u.f;o>l;)for(var h,d=s(arguments[l++]),v=f?i(d).concat(f(d)):i(d),g=v.length,m=0;g>m;)h=v[m++],r&&!p.call(d,h)||(n[h]=d[h]);return n}:l},YVoz:function(t,e,n){"use strict";t.exports=Object.assign},YwZP:function(t,e,n){"use strict";n.r(e),n.d(e,"Link",(function(){return I})),n.d(e,"Location",(function(){return b})),n.d(e,"LocationProvider",(function(){return w})),n.d(e,"Match",(function(){return U})),n.d(e,"Redirect",(function(){return N})),n.d(e,"Router",(function(){return R})),n.d(e,"ServerLocation",(function(){return x})),n.d(e,"isRedirect",(function(){return A})),n.d(e,"redirectTo",(function(){return D})),n.d(e,"useLocation",(function(){return W})),n.d(e,"useNavigate",(function(){return F})),n.d(e,"useParams",(function(){return q})),n.d(e,"useMatch",(function(){return Y})),n.d(e,"BaseContext",(function(){return S}));var r=n("q1tI"),o=n.n(r),i=(n("17x9"),n("QLaP")),a=n.n(i),u=n("nqlD"),c=n.n(u),s=n("94VI"),l=n("LYrO");n.d(e,"matchPath",(function(){return l.match}));var f=n("9Xx/");n.d(e,"createHistory",(function(){return f.a})),n.d(e,"createMemorySource",(function(){return f.b})),n.d(e,"navigate",(function(){return f.d})),n.d(e,"globalHistory",(function(){return f.c}));var p=Object.assign||function(t){for(var e=1;e=0||Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]);return n}function d(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function v(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}function g(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}var m=function(t,e){var n=c()(e);return n.displayName=t,n},y=m("Location"),b=function(t){var e=t.children;return o.a.createElement(y.Consumer,null,(function(t){return t?e(t):o.a.createElement(w,null,e)}))},w=function(t){function e(){var n,r;d(this,e);for(var o=arguments.length,i=Array(o),a=0;a-1?(i=e.substring(0,r),a=e.substring(r)):i=e,o.a.createElement(y.Provider,{value:{location:{pathname:i,search:a,hash:""},navigate:function(){throw new Error("You can't call navigate on the server.")}}},n)},S=m("Base",{baseuri:"/",basepath:"/"}),R=function(t){return o.a.createElement(S.Consumer,null,(function(e){return o.a.createElement(b,null,(function(n){return o.a.createElement(E,p({},e,n,t))}))}))},E=function(t){function e(){return d(this,e),v(this,t.apply(this,arguments))}return g(e,t),e.prototype.render=function(){var t=this.props,e=t.location,n=t.navigate,r=t.basepath,i=t.primary,a=t.children,u=(t.baseuri,t.component),c=void 0===u?"div":u,s=h(t,["location","navigate","basepath","primary","children","baseuri","component"]),f=o.a.Children.toArray(a).reduce((function(t,e){var n=G(r)(e);return t.concat(n)}),[]),d=e.pathname,v=Object(l.pick)(f,d);if(v){var g=v.params,m=v.uri,y=v.route,b=v.route.value;r=y.default?r:y.path.replace(/\*$/,"");var w=p({},g,{uri:m,location:e,navigate:function(t,e){return n(Object(l.resolve)(t,m),e)}}),x=o.a.cloneElement(b,w,b.props.children?o.a.createElement(R,{location:e,primary:i},b.props.children):void 0),E=i?P:c,O=i?p({uri:m,location:e,component:c},s):s;return o.a.createElement(S.Provider,{value:{baseuri:m,basepath:r}},o.a.createElement(E,O,x))}return null},e}(o.a.PureComponent);E.defaultProps={primary:!0};var O=m("Focus"),P=function(t){var e=t.uri,n=t.location,r=t.component,i=h(t,["uri","location","component"]);return o.a.createElement(O.Consumer,null,(function(t){return o.a.createElement(_,p({},i,{component:r,requestFocus:t,uri:e,location:n}))}))},j=!0,k=0,_=function(t){function e(){var n,r;d(this,e);for(var o=arguments.length,i=Array(o),a=0;a=s?t?"":void 0:(i=u.charCodeAt(c))<55296||i>56319||c+1===s||(a=u.charCodeAt(c+1))<56320||a>57343?t?u.charAt(c):i:t?u.slice(c,c+2):a-56320+(i-55296<<10)+65536}};t.exports={codeAt:i(!1),charAt:i(!0)}},ZWaQ:function(t,e,n){"use strict";var r=n("m/L8").f,o=n("fHMY"),i=n("4syw"),a=n("A2ZE"),u=n("GarU"),c=n("ImZN"),s=n("fdAy"),l=n("JiZb"),f=n("g6v/"),p=n("8YOa").fastKey,h=n("afO8"),d=h.set,v=h.getterFor;t.exports={getConstructor:function(t,e,n,s){var l=t((function(t,r){u(t,l,e),d(t,{type:e,index:o(null),first:void 0,last:void 0,size:0}),f||(t.size=0),null!=r&&c(r,t[s],{that:t,AS_ENTRIES:n})})),h=v(e),g=function(t,e,n){var r,o,i=h(t),a=m(t,e);return a?a.value=n:(i.last=a={index:o=p(e,!0),key:e,value:n,previous:r=i.last,next:void 0,removed:!1},i.first||(i.first=a),r&&(r.next=a),f?i.size++:t.size++,"F"!==o&&(i.index[o]=a)),t},m=function(t,e){var n,r=h(t),o=p(e);if("F"!==o)return r.index[o];for(n=r.first;n;n=n.next)if(n.key==e)return n};return i(l.prototype,{clear:function(){for(var t=h(this),e=t.index,n=t.first;n;)n.removed=!0,n.previous&&(n.previous=n.previous.next=void 0),delete e[n.index],n=n.next;t.first=t.last=void 0,f?t.size=0:this.size=0},delete:function(t){var e=h(this),n=m(this,t);if(n){var r=n.next,o=n.previous;delete e.index[n.index],n.removed=!0,o&&(o.next=r),r&&(r.previous=o),e.first==n&&(e.first=r),e.last==n&&(e.last=o),f?e.size--:this.size--}return!!n},forEach:function(t){for(var e,n=h(this),r=a(t,arguments.length>1?arguments[1]:void 0,3);e=e?e.next:n.first;)for(r(e.value,e.key,this);e&&e.removed;)e=e.previous},has:function(t){return!!m(this,t)}}),i(l.prototype,n?{get:function(t){var e=m(this,t);return e&&e.value},set:function(t,e){return g(this,0===t?0:t,e)}}:{add:function(t){return g(this,t=0===t?0:t,t)}}),f&&r(l.prototype,"size",{get:function(){return h(this).size}}),l},setStrong:function(t,e,n){var r=e+" Iterator",o=v(e),i=v(r);s(t,e,(function(t,e){d(this,{type:r,target:t,state:o(t),kind:e,last:void 0})}),(function(){for(var t=i(this),e=t.kind,n=t.last;n&&n.removed;)n=n.previous;return t.target&&(t.last=n=n?n.next:t.state.first)?"keys"==e?{value:n.key,done:!1}:"values"==e?{value:n.value,done:!1}:{value:[n.key,n.value],done:!1}:(t.target=void 0,{value:void 0,done:!0})}),n?"entries":"values",!n,!0),l(e)}}},afO8:function(t,e,n){var r,o,i,a=n("f5p1"),u=n("2oRo"),c=n("hh1v"),s=n("kRJp"),l=n("UTVS"),f=n("xs3f"),p=n("93I0"),h=n("0BK2"),d=u.WeakMap;if(a){var v=f.state||(f.state=new d),g=v.get,m=v.has,y=v.set;r=function(t,e){return e.facade=t,y.call(v,t,e),e},o=function(t){return g.call(v,t)||{}},i=function(t){return m.call(v,t)}}else{var b=p("state");h[b]=!0,r=function(t,e){return e.facade=t,s(t,b,e),e},o=function(t){return l(t,b)?t[b]:{}},i=function(t){return l(t,b)}}t.exports={set:r,get:o,has:i,enforce:function(t){return i(t)?o(t):r(t,{})},getterFor:function(t){return function(e){var n;if(!c(e)||(n=o(e)).type!==t)throw TypeError("Incompatible receiver, "+t+" required");return n}}}},bWFh:function(t,e,n){"use strict";var r=n("I+eb"),o=n("2oRo"),i=n("lMq5"),a=n("busE"),u=n("8YOa"),c=n("ImZN"),s=n("GarU"),l=n("hh1v"),f=n("0Dky"),p=n("HH4o"),h=n("1E5z"),d=n("cVYH");t.exports=function(t,e,n){var v=-1!==t.indexOf("Map"),g=-1!==t.indexOf("Weak"),m=v?"set":"add",y=o[t],b=y&&y.prototype,w=y,x={},S=function(t){var e=b[t];a(b,t,"add"==t?function(t){return e.call(this,0===t?0:t),this}:"delete"==t?function(t){return!(g&&!l(t))&&e.call(this,0===t?0:t)}:"get"==t?function(t){return g&&!l(t)?void 0:e.call(this,0===t?0:t)}:"has"==t?function(t){return!(g&&!l(t))&&e.call(this,0===t?0:t)}:function(t,n){return e.call(this,0===t?0:t,n),this})};if(i(t,"function"!=typeof y||!(g||b.forEach&&!f((function(){(new y).entries().next()})))))w=n.getConstructor(e,t,v,m),u.REQUIRED=!0;else if(i(t,!0)){var R=new w,E=R[m](g?{}:-0,1)!=R,O=f((function(){R.has(1)})),P=p((function(t){new y(t)})),j=!g&&f((function(){for(var t=new y,e=5;e--;)t[m](e,e);return!t.has(-0)}));P||((w=e((function(e,n){s(e,w,t);var r=d(new y,e,w);return null!=n&&c(n,r[m],{that:r,AS_ENTRIES:v}),r}))).prototype=b,b.constructor=w),(O||j)&&(S("delete"),S("has"),v&&S("get")),(j||E)&&S(m),g&&b.clear&&delete b.clear}return x[t]=w,r({global:!0,forced:w!=y},x),h(w,t),g||n.setStrong(w,t,v),w}},busE:function(t,e,n){var r=n("2oRo"),o=n("kRJp"),i=n("UTVS"),a=n("zk60"),u=n("iSVu"),c=n("afO8"),s=c.get,l=c.enforce,f=String(String).split("String");(t.exports=function(t,e,n,u){var c,s=!!u&&!!u.unsafe,p=!!u&&!!u.enumerable,h=!!u&&!!u.noTargetGet;"function"==typeof n&&("string"!=typeof e||i(n,"name")||o(n,"name",e),(c=l(n)).source||(c.source=f.join("string"==typeof e?e:""))),t!==r?(s?!h&&t[e]&&(p=!0):delete t[e],p?t[e]=n:o(t,e,n)):p?t[e]=n:a(e,n)})(Function.prototype,"toString",(function(){return"function"==typeof this&&s(this).source||u(this)}))},cDf5:function(t,e){function n(e){return"function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?t.exports=n=function(t){return typeof t}:t.exports=n=function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},n(e)}t.exports=n},cSJ8:function(t,e,n){"use strict";n.d(e,"a",(function(){return r}));n("LKBx");function r(t,e){return void 0===e&&(e=""),e?t===e?"/":t.startsWith(e+"/")?t.slice(e.length):t:t}},cVYH:function(t,e,n){var r=n("hh1v"),o=n("0rvr");t.exports=function(t,e,n){var i,a;return o&&"function"==typeof(i=e.constructor)&&i!==n&&r(a=i.prototype)&&a!==n.prototype&&o(t,a),t}},cu4x:function(t,e,n){"use strict";e.__esModule=!0,e.parsePath=function(t){var e=t||"/",n="",r="",o=e.indexOf("#");-1!==o&&(r=e.substr(o),e=e.substr(0,o));var i=e.indexOf("?");-1!==i&&(n=e.substr(i),e=e.substr(0,i));return{pathname:e,search:"?"===n?"":n,hash:"#"===r?"":r}}},"dBg+":function(t,e){e.f=Object.getOwnPropertySymbols},"eDl+":function(t,e){t.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},emEt:function(t,e,n){"use strict";n.r(e),n.d(e,"PageResourceStatus",(function(){return l})),n.d(e,"BaseLoader",(function(){return g})),n.d(e,"ProdLoader",(function(){return y})),n.d(e,"setLoader",(function(){return b})),n.d(e,"publicLoader",(function(){return w})),n.d(e,"getStaticQueryResults",(function(){return x}));var r=n("VbXa"),o=n.n(r),i=(n("zKZe"),n("YGK4"),n("3bBZ"),n("4mDm"),n("PKPk"),n("Tskq"),n("yq1k"),n("JTJg"),n("5s+n"),n("07d7"),n("inlA"),n("tkto"),function(t){if("undefined"==typeof document)return!1;var e=document.createElement("link");try{if(e.relList&&"function"==typeof e.relList.supports)return e.relList.supports(t)}catch(n){return!1}return!1}("prefetch")?function(t,e){return new Promise((function(n,r){if("undefined"!=typeof document){var o=document.createElement("link");o.setAttribute("rel","prefetch"),o.setAttribute("href",t),Object.keys(e).forEach((function(t){o.setAttribute(t,e[t])})),o.onload=n,o.onerror=r,(document.getElementsByTagName("head")[0]||document.getElementsByName("script")[0].parentNode).appendChild(o)}else r()}))}:function(t){return new Promise((function(e,n){var r=new XMLHttpRequest;r.open("GET",t,!0),r.onload=function(){200===r.status?e():n()},r.send(null)}))}),a={},u=function(t,e){return new Promise((function(n){a[t]?n():i(t,e).then((function(){n(),a[t]=!0})).catch((function(){}))}))},c=n("5yr3"),s=n("30RF"),l={Error:"error",Success:"success"},f=function(t){return t&&t.default||t},p=function(t){var e;return"/page-data/"+("/"===t?"index":e=(e="/"===(e=t)[0]?e.slice(1):e).endsWith("/")?e.slice(0,-1):e)+"/page-data.json"};function h(t,e){return void 0===e&&(e="GET"),new Promise((function(n,r){var o=new XMLHttpRequest;o.open(e,t,!0),o.onreadystatechange=function(){4==o.readyState&&n(o)},o.send(null)}))}var d,v=function(t,e){void 0===e&&(e=null);var n={componentChunkName:t.componentChunkName,path:t.path,webpackCompilationHash:t.webpackCompilationHash,matchPath:t.matchPath,staticQueryHashes:t.staticQueryHashes};return{component:e,json:t.result,page:n}},g=function(){function t(t,e){this.inFlightNetworkRequests=new Map,this.pageDb=new Map,this.inFlightDb=new Map,this.staticQueryDb={},this.pageDataDb=new Map,this.prefetchTriggered=new Set,this.prefetchCompleted=new Set,this.loadComponent=t,Object(s.d)(e)}var e=t.prototype;return e.memoizedGet=function(t){var e=this,n=this.inFlightNetworkRequests.get(t);return n||(n=h(t,"GET"),this.inFlightNetworkRequests.set(t,n)),n.then((function(n){return e.inFlightNetworkRequests.delete(t),n})).catch((function(n){throw e.inFlightNetworkRequests.delete(t),n}))},e.setApiRunner=function(t){this.apiRunner=t,this.prefetchDisabled=t("disableCorePrefetching").some((function(t){return t}))},e.fetchPageDataJson=function(t){var e=this,n=t.pagePath,r=t.retries,o=void 0===r?0:r,i=p(n);return this.memoizedGet(i).then((function(r){var i=r.status,a=r.responseText;if(200===i)try{var u=JSON.parse(a);if(void 0===u.path)throw new Error("not a valid pageData response");return Object.assign(t,{status:l.Success,payload:u})}catch(c){}return 404===i||200===i?"/404.html"===n?Object.assign(t,{status:l.Error}):e.fetchPageDataJson(Object.assign(t,{pagePath:"/404.html",notFound:!0})):500===i?Object.assign(t,{status:l.Error}):o<3?e.fetchPageDataJson(Object.assign(t,{retries:o+1})):Object.assign(t,{status:l.Error})}))},e.loadPageDataJson=function(t){var e=this,n=Object(s.b)(t);if(this.pageDataDb.has(n)){var r=this.pageDataDb.get(n);return Promise.resolve(r)}return this.fetchPageDataJson({pagePath:n}).then((function(t){return e.pageDataDb.set(n,t),t}))},e.findMatchPath=function(t){return Object(s.a)(t)},e.loadPage=function(t){var e=this,n=Object(s.b)(t);if(this.pageDb.has(n)){var r=this.pageDb.get(n);return Promise.resolve(r.payload)}if(this.inFlightDb.has(n))return this.inFlightDb.get(n);var o=Promise.all([this.loadAppData(),this.loadPageDataJson(n)]).then((function(t){var r=t[1];if(r.status===l.Error)return{status:l.Error};var o=r.payload,i=o,a=i.componentChunkName,u=i.staticQueryHashes,s=void 0===u?[]:u,f={},p=e.loadComponent(a).then((function(e){var n;return f.createdAt=new Date,e?(f.status=l.Success,!0===r.notFound&&(f.notFound=!0),o=Object.assign(o,{webpackCompilationHash:t[0]?t[0].webpackCompilationHash:""}),n=v(o,e)):f.status=l.Error,n})),h=Promise.all(s.map((function(t){if(e.staticQueryDb[t]){var n=e.staticQueryDb[t];return{staticQueryHash:t,jsonPayload:n}}return e.memoizedGet("/page-data/sq/d/"+t+".json").then((function(e){var n=JSON.parse(e.responseText);return{staticQueryHash:t,jsonPayload:n}}))}))).then((function(t){var n={};return t.forEach((function(t){var r=t.staticQueryHash,o=t.jsonPayload;n[r]=o,e.staticQueryDb[r]=o})),n}));return Promise.all([p,h]).then((function(t){var r,o=t[0],i=t[1];return o&&(r=Object.assign({},o,{staticQueryResults:i}),f.payload=r,c.a.emit("onPostLoadPageResources",{page:r,pageResources:r})),e.pageDb.set(n,f),r}))}));return o.then((function(t){e.inFlightDb.delete(n)})).catch((function(t){throw e.inFlightDb.delete(n),t})),this.inFlightDb.set(n,o),o},e.loadPageSync=function(t){var e=Object(s.b)(t);if(this.pageDb.has(e))return this.pageDb.get(e).payload},e.shouldPrefetch=function(t){return!!function(){if("connection"in navigator&&void 0!==navigator.connection){if((navigator.connection.effectiveType||"").includes("2g"))return!1;if(navigator.connection.saveData)return!1}return!0}()&&!this.pageDb.has(t)},e.prefetch=function(t){var e=this;if(!this.shouldPrefetch(t))return!1;if(this.prefetchTriggered.has(t)||(this.apiRunner("onPrefetchPathname",{pathname:t}),this.prefetchTriggered.add(t)),this.prefetchDisabled)return!1;var n=Object(s.b)(t);return this.doPrefetch(n).then((function(){e.prefetchCompleted.has(t)||(e.apiRunner("onPostPrefetchPathname",{pathname:t}),e.prefetchCompleted.add(t))})),!0},e.doPrefetch=function(t){var e=this,n=p(t);return u(n,{crossOrigin:"anonymous",as:"fetch"}).then((function(){return e.loadPageDataJson(t)}))},e.hovering=function(t){this.loadPage(t)},e.getResourceURLsForPathname=function(t){var e=Object(s.b)(t),n=this.pageDataDb.get(e);if(n){var r=v(n.payload);return[].concat(m(r.page.componentChunkName),[p(e)])}return null},e.isPageNotFound=function(t){var e=Object(s.b)(t),n=this.pageDb.get(e);return!n||n.notFound},e.loadAppData=function(t){var e=this;return void 0===t&&(t=0),this.memoizedGet("/page-data/app-data.json").then((function(n){var r,o=n.status,i=n.responseText;if(200!==o&&t<3)return e.loadAppData(t+1);if(200===o)try{var a=JSON.parse(i);if(void 0===a.webpackCompilationHash)throw new Error("not a valid app-data response");r=a}catch(u){}return r}))},t}(),m=function(t){return(window.___chunkMapping[t]||[]).map((function(t){return""+t}))},y=function(t){function e(e,n){return t.call(this,(function(t){return e.components[t]?e.components[t]().then(f).catch((function(){return null})):Promise.resolve()}),n)||this}o()(e,t);var n=e.prototype;return n.doPrefetch=function(e){return t.prototype.doPrefetch.call(this,e).then((function(t){if(t.status!==l.Success)return Promise.resolve();var e=t.payload,n=e.componentChunkName,r=m(n);return Promise.all(r.map(u)).then((function(){return e}))}))},n.loadPageDataJson=function(e){return t.prototype.loadPageDataJson.call(this,e).then((function(t){return t.notFound?h(e,"HEAD").then((function(e){return 200===e.status?{status:l.Error}:t})):t}))},e}(g),b=function(t){d=t},w={getResourcesForPathname:function(t){return console.warn("Warning: getResourcesForPathname is deprecated. Use loadPage instead"),d.i.loadPage(t)},getResourcesForPathnameSync:function(t){return console.warn("Warning: getResourcesForPathnameSync is deprecated. Use loadPageSync instead"),d.i.loadPageSync(t)},enqueue:function(t){return d.prefetch(t)},getResourceURLsForPathname:function(t){return d.getResourceURLsForPathname(t)},loadPage:function(t){return d.loadPage(t)},loadPageSync:function(t){return d.loadPageSync(t)},prefetch:function(t){return d.prefetch(t)},isPageNotFound:function(t){return d.isPageNotFound(t)},hovering:function(t){return d.hovering(t)},loadAppData:function(){return d.loadAppData()}};e.default=w;function x(){return d?d.staticQueryDb:{}}},ewvW:function(t,e,n){var r=n("HYAF");t.exports=function(t){return Object(r(t))}},f5p1:function(t,e,n){var r=n("2oRo"),o=n("iSVu"),i=r.WeakMap;t.exports="function"==typeof i&&/native code/.test(o(i))},fHMY:function(t,e,n){var r,o=n("glrk"),i=n("N+g0"),a=n("eDl+"),u=n("0BK2"),c=n("G+Rx"),s=n("zBJ4"),l=n("93I0"),f=l("IE_PROTO"),p=function(){},h=function(t){return" \ No newline at end of file diff --git a/manifest.webmanifest b/manifest.webmanifest new file mode 100644 index 0000000..24619dd --- /dev/null +++ b/manifest.webmanifest @@ -0,0 +1 @@ +{"name":"gatsby-starter-hello-friend","short_name":"맨땅에 코딩","start_url":"/","background_color":"#292a2d","theme_color":"#292a2d","display":"minimal-ui","cacheDigest":"0f3184a956c4660a6190654597374554","icons":[{"src":"icons/icon-48x48.png?v=0f3184a956c4660a6190654597374554","sizes":"48x48","type":"image/png"},{"src":"icons/icon-72x72.png?v=0f3184a956c4660a6190654597374554","sizes":"72x72","type":"image/png"},{"src":"icons/icon-96x96.png?v=0f3184a956c4660a6190654597374554","sizes":"96x96","type":"image/png"},{"src":"icons/icon-144x144.png?v=0f3184a956c4660a6190654597374554","sizes":"144x144","type":"image/png"},{"src":"icons/icon-192x192.png?v=0f3184a956c4660a6190654597374554","sizes":"192x192","type":"image/png"},{"src":"icons/icon-256x256.png?v=0f3184a956c4660a6190654597374554","sizes":"256x256","type":"image/png"},{"src":"icons/icon-384x384.png?v=0f3184a956c4660a6190654597374554","sizes":"384x384","type":"image/png"},{"src":"icons/icon-512x512.png?v=0f3184a956c4660a6190654597374554","sizes":"512x512","type":"image/png"}]} \ No newline at end of file diff --git a/page-data/1/page-data.json b/page-data/1/page-data.json new file mode 100644 index 0000000..ce3bdac --- /dev/null +++ b/page-data/1/page-data.json @@ -0,0 +1 @@ +{"componentChunkName":"component---src-templates-page-js","path":"/1","result":{"data":{"markdownRemark":{"frontmatter":{"title":"ATDD 프로세스","date":"29 November 2019","path":"/1","author":"Boorownie","excerpt":null,"tags":["ATDD","Agile"],"series":null,"coverImage":null},"id":"ae02313e-fb02-5450-8223-74c275b8e87a","html":"

ATDD(Acceptance Test Driven Development)는, 용어를 그대로 풀어서 해석하면, 인수 테스트 주도 개발으로서\n인수 테스트를 먼저 작성한 다음 기능 개발을 하는 방법입니다.\n여기서 말하는 인수는 클라이언트가 요청한 소프트웨어의 결과물을 넘겨 받는다는 의미이며, 인수 테스트는 요구사항을 모두 만족하여 기능 완료 여부를 확인하는 테스트라고 할 수 있습니다.

\n

단순히 인수 테스트를 만들기 위해 ATDD를 도입하는 것은 아닙니다.\nATDD의 뜻을 찾아보면 사용자(고객)-개발자-테스터간의 커뮤니케이션을 기반한 개발 방법이라고 정의됩니다.\n커뮤니케이션을 위해서 인수 테스트를 만든다는 뜻인데 이 둘의 연관관계를 연상하는 것이 어려울 수 있습니다.\n이번 포스팅에서는 인수 테스트를 만드는 ATDD의 궁극적인 목표와 ATDD 프로세스를 예시를 통해 알아보도록 하겠습니다.

\n

애자일 관점에서의 ATDD

\n\n

애자일 방법론에서는 미리 예측하며 개발을 하지 않습니다. 일정한 주기를 가지고 끊임없이 프로토 타입을 만들어냅니다.\n필요한 부분이 발생하면, 그때 기능 목록을 추가하고 수정하여 하나의 커다란 소프트웨어를 개발해 나갑니다.[link]\n이러한 애자일의 프로그밍 방법론 중 하나인 ATDD는 사용자 스토리를 기반으로\n인수 조건을 도출하여\n기능 개발을 진행하는 방법론 입니다.

\n

\n

ATDD에서는 사용자(고객)-개발자-테스터, 즉 팀원들이 각 포지션의 관점으로 함께 논의하여 실행 가능한 예제를 도출합니다.\n이러한 과정은 사용자(고객)들에게 요구사항을 명확히 하는데 도움을 주고, 개발자에게는 코드 구현의 방향성과 목적을 가지는데 도움을 주고, 테스터에게는 단순히 기능적 테스트를 하는 것 보다 좀 더 나은 계획을 세울 수 있게 도와줍니다.\n결국 ATDD는 팀원간에 요구사항을 같은 수준으로 이해하는데 큰 도움을 줍니다.\n요구사항이 무엇이고 기능 완료 기준이 무엇인지에 대해 하나의 공통된 생각을 가질 수 있게 합니다.\n일정한 주기를 가지고 진행하는 \"스프린트\"에서는 이러한 공통의 기준들이 매우 중요합니다.[link]

\n
\n

즉, 스프린트에서는 팀원들이 공통의 기준을 가지고 진행하는것이 중요합니다. 사용자 스토리와 인수 조건, 인수 테스트 작성을 모든 개발 팀원이 함께 논의하여 요구사항에 대한 공통의 이해를 할 수 있게 도와줍니다.

\n
\n

ATDD 프로세스

\n

\n

ATDD는 4가지의 행위(Discuss, Distill, Develop, Demo)와\n4가지의 산출물(User Story, Acceptance Criteria, Tests, Working Software),\n하나의 결과(Business Value)로 설명할 수 있습니다.[link]\nATDD Cycle을 바탕으로 서핑 용품 대여 서비스를 개발해 나가는 과정을 따라가며 ATDD 프로세스에 대해서 알아보겠습니다.

\n

사용자 스토리 작성하기

\n
    \n
  • 사용자 스토리는 who, why, what을 기준으로 작성합니다.[link]
  • \n
  • \n

    결과물: 사용자 스토리

    \n
    \n

    서핑샵 손님들은 서핑 용품을 빌리기 위해 대여 신청을 하고 싶다.

    \n
    \n
  • \n
\n

Discuss

\n
    \n
  • 사용자 스토리를 바탕으로 예시만들고, 이를 통해 인수 조건을 도출합니다. 인수 조건 잘 도출하는 법
  • \n
  • 인수 조건은 기능 완료 조건으로서 사용자 스토리 보다 조금 더 구체적인 시나리오를 통해 기술합니다.
  • \n
  • \n

    결과물: 인수 조건

    \n
    \n
      \n
    • 날짜별로 대여가 가능한 용품을 조회할 수 있어야 한다.
    • \n
    • 대여를 희망하는 용품을 희망하는 날짜에 신청 할 수 있어야 한다.
    • \n
    • 대여 신청한 내역을 사장님이 확인 할 수 있어야 한다.
    • \n
    \n
    \n
  • \n
\n

Distill

\n
    \n
  • 도출된 인수 조건을 통해 인수 테스트를 작성합니다.
  • \n
  • 인수 테스트는 사용자의 관점을 나타내며 시스템 작동 방식을 설명하기 위한 요구 사항의 형태로 작동하며 시스템이 의도 한대로 작동하는지 확인하는 방법으로도 사용됩니다.
  • \n
  • 테스트는 Given-When-Then 형식으로 작성을 하되 해당사항이 없는 부분은 생략할 수 있습니다.
  • \n
  • 성공 케이스와 실패 케이스를 정의하고 요청/응답에 대한 DTO를 결정합니다.
  • \n
  • 인수 테스트 작성의 기준은 팀에서 정합니다. (ex. 인수 테스트는 기능별로 만들고 Controller 테스트를 통해 에러 케이스를 검증한다.)
  • \n
  • \n

    결과물: 인수 테스트

    \n
    @DisplayName(\"날짜별로 대여가 가능한 용품을 조회할 수 있어야 한다.\")\n@Test\npublic void showItems() {\n this.webTestClient.get()\n         .uri(uriBuilder -> uriBuilder.path(\"/items\")\n                 .queryParam(\"date\", \"20191127\")\n                 .build())\n         .accept(MediaType.APPLICATION_JSON)\n         .exchange()\n         .expectStatus().isOk()\n         .expectHeader().contentType(MediaType.APPLICATION_JSON)\n         .expectBody()\n         .jsonPath(\"$\").isNotEmpty();\n}
    \n
  • \n
\n

테스트 시나리오의 품질관리

\n

\n기술적인 행위를 바탕으로 시나리오를 작성 할 경우 기술과 관련된 용어가 나타나고 구현과 관련된 기술이 바뀌거나 시스템을 사용하는 순서가 바뀌면 테스트 시나리오를 수정해야 합니다.\n작업 흐름을 바탕으로 시나리오를 작성 할 경우 기술이 바뀌더라도 테스트 시나리오가 바뀌지 않지만, 작업 흐름이 변경되면 테스트 시나리오를 수정해야 합니다.\n비즈니스 규칙을 명확히 드러나도록 테스트 시나리오를 작성하면 구현과 관련 기술이 변경되어도, 작업 흐름이 바뀌어도 테스트 시나리오를 변경하지 않아도 됩니다.\n[6]

\n
Technical Activity
\n
    \n
  1. 대여 가능한 용품을 조회할 수 있는 페이지로 접속한다.
  2. \n
  3. 조회하고자 하는 날짜를 \"20191127\"이라고 입력한다.
  4. \n
  5. 조회하기 버튼을 누른다.
  6. \n
  7. 결과 페이지에서 대여가능한 용품 목록을 확인한다.
  8. \n
\n
Workflow
\n
    \n
  1. 대여 가능한 용품을 조회할 수 있는 페이지로 접속한다.
  2. \n
  3. 조회하고자 하는 날짜를 2019년 11월 27일로 설정한다.
  4. \n
  5. 조회한다.
  6. \n
  7. 대여가능한 용품 목록 확인한다.
  8. \n
\n
Business Rule
\n
    \n
  • Scenario: 특정 일자에 대여 가능한 물품 정보를 제공한다.
  • \n
  • When: 2019년 11월 27일 대여 가능한 물품을 조회한다.
  • \n
  • Then: 2019년 11월 27일 대여 가능한 물품 목록을 확인할 수 있다.
  • \n
\n

Develop

\n
    \n
  • \n

    문서화

    \n
      \n
    • 벡엔드와 프론트의 작업을 병렬로 진행할 수 있도록 도움을 줍니다.
    • \n
    • 문서화 할 기능의 기준은 팀에서 정합니다. (ex. 사이드 케이스 / 실패 케이스에 대해서는 내부항목으로 기재한다.)
    • \n
    \n
  • \n
  • \n

    TDD로 개발하기

    \n
      \n
    • 인수 테스트 기반으로 하나씩 기능개발을 진행합니다.
    • \n
    • TDD 프로세스에 따라 테스트를 먼저 작성하고 프로덕션 코드를 작성합니다.
    • \n
    \n
  • \n
  • 결과물: 소프트웨어 결과물
  • \n
\n

Demo

\n
    \n
  • 인수 테스트가 성공이 되면 해당 이슈는 완료합니다. (애자일에서는 검증 단계가 필요 없을까?)
  • \n
  • 완료된 인수 테스트 수에 따라서 개발 진행 상황을 인지할 수 있습니다.
  • \n
","excerpt":"ATDD(Acceptance Test Driven Development…"}},"pageContext":{"type":"posts","next":null,"previous":{"frontmatter":{"path":"/2","title":"ATDD with Spring Boot","tags":["ATDD","Spring","Spring Boot"]},"fileAbsolutePath":"/Users/brown/workspaces/blog/boorownie.github.io/src/posts/2.md"}}},"staticQueryHashes":["1425477374","3128451518"]} \ No newline at end of file diff --git a/page-data/2/page-data.json b/page-data/2/page-data.json new file mode 100644 index 0000000..feda977 --- /dev/null +++ b/page-data/2/page-data.json @@ -0,0 +1 @@ +{"componentChunkName":"component---src-templates-page-js","path":"/2","result":{"data":{"markdownRemark":{"frontmatter":{"title":"ATDD with Spring Boot","date":"30 November 2019","path":"/2","author":"Boorownie","excerpt":null,"tags":["ATDD","Spring","Spring Boot"],"series":null,"coverImage":null},"id":"13f27efa-7fb7-5520-816d-784dfe9daae2","html":"

\n

TDD를 배우면서 도메인에 먼저 집중하여 도메인 기능의 테스트 코드를 구현한 후 기능을 구현하는 방법으로 학습합니다.\n한 번에 한 가지를 집중하고 각 도메인들은 애플리케이션이 구성되기 전에 작성됩니다.\n하지만 ATDD에서 인수 테스트는 큰 그림을 그려 구조를 형성한 다음 구체적인 부분을 구현합니다.\n이러한 접근 방법을 전자는 Inside out이라 하고 후자는 Outside in이라고 합니다.(TDD가 Inside out이고 ATDD가 Outside in이라는 뜻은 아닙니다.)\nTDD를 이미 접한 후 ATDD를 배우는 사람은 각 접근 방식의 차이점을 인지하지 못하여 학습에 어려움을 겪기도 합니다.\nInside out 접근 방식은 도메인 지식이 있거나 요구 사항이 단순한 경우 적합하고\nOutside in 접근 방식은 도메인 지식이 없거나 요구 사항 복잡도가 높은 경우 적합하다 할 수 있습니다.\n각 방식의 장단점은 TDD - From the Inside Out or the Outside In? 에서 확인할 수 있습니다.\n이번 포스팅에서는 간단한 예제를 통해 Outside in방식의 ATDD 개발 방법에 대해서 알아보겠습니다.

\n

ATDD 개발 프로세스

\n
    \n
  1. 인수 조건을 바탕으로 인수 테스트 작성한다.
  2. \n
  3. 인수 테스트를 바탕으로 문서화를 하여 인터페이스를 공유한다.
  4. \n
  5. 인수 테스트를 동작시키는 기능을 하나씩 구현한다.
  6. \n
\n

[예제] 서핑 용품 대여 신청 기능 개발

\n
\n

상황에 따라 테스트 방법이 달라질 수 있고 아래의 예시는 그 방법 중 하나입니다.

\n
\n

서핑 용품 대여 신청 기능을 구현하는 과정을 ATDD 개발 프로세스로 단계별로 알아보겠습니다.\n예제에서는 WebTestClient로 Happy case에 대한 인수 테스트를 만들고\n예외적인 상황이나 에러 처리 테스트는 Controller Test의 MockMvc객체를 이용할 예정입니다.\n(추후 Github 공유 예정)

\n

인수 조건

\n
\n

대여를 희망하는 용품을 희망하는 날짜에 신청 할 수 있어야 한다.

\n
\n

인수 테스트 작성

\n

이전 포스팅에서 언급되었던 것 처럼, 비즈니스 규칙(Business Rule)은 기술의 구현이나 작업흐름만큼 변경이 많지 않습니다.\n인수 테스트를 작성할 때 비즈니스 규칙을 고려하여 테스트 시나리오를 만들 경우 변경사항에 비교적으로 유연하게 대처할 수 있습니다.\n그런 관점에서 볼 때, UI 인수 테스트를 작성하는 것 보다 API 인수 테스트를 하는게 시나리오의 품질관리에 유리합니다.\n따라서 UI 테스트 보다는 API 테스트를 통해 인수 테스트를 작성하겠습니다.

\n
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)\n@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)\n@AutoConfigureWebTestClient\npublic class RentalAcceptanceTest {\n    public static final String LOCATION = \"Location\";\n\n    @Autowired\n    private WebTestClient webTestClient;\n\n    @Test\n    public void createRental() {\n        String inputJson = \"{\\\"dateTime\\\":\\\"\" + \"2019120112\" + \"\\\", \" +\n                \"\\\"itemId\\\":\\\"\" + \"110920\" + \"\\\", \" +\n                \"\\\"itemType\\\":\\\"\" + \"14\" + \"\\\"}\";\n\n        this.webTestClient.post().uri(\"/rentals\")\n                .contentType(MediaType.APPLICATION_JSON) // 요청으로 보내는 데이터 유형 명시\n                .accept(MediaType.APPLICATION_JSON) // 응답으로 받고 싶은 데이터 유형 명시\n                .body(Mono.just(inputJson), String.class)\n                .exchange()\n                .expectStatus().isCreated()\n                .expectHeader().contentType(MediaType.APPLICATION_JSON)\n                .expectHeader().valueMatches(LOCATION, \"\\\\/rentals\\\\/\\\\d\")\n                .expectBody()\n                .jsonPath(\"$\").isNotEmpty()\n                .jsonPath(\"$.id\").isNotEmpty()\n                .jsonPath(\"$.status\").isEqualTo(\"READY\");\n    }\n}
\n

문서화

\n

Mock 서버를 먼저 개발하여 Mock 데이터를 제공하면 백엔드와 프론트엔드 개발이 병렬적으로 진행이 가능합니다.\n이 때, 인수 테스트를 기반으로 문서화까지 해서 제공한다면 더 효과적인 커뮤니케이션이 가능합니다.\n따라서 Spring Rest Docs를 이용하여 문서를 만들 예정입니다.\nRest Docs를 이용하여 문서를 만들기 위해서는 테스트가 성공되어야 합니다.\n따라서 인수 테스트를 성공시키기 위한 Mock 서버를 구현하고 Request / Response DTO를 선언합니다.\n이 부분은 ATDD 프로세스에 위배될 수 있지만 업무 효율을 고려하여 유리하다고 판단하여 이렇게 결정하였습니다.

\n

Mock 서버 구현

\n
@RestController\npublic class RentalController {\n    @PostMapping(\"/rentals\")\n    public ResponseEntity createArticles(@RequestBody RentalRequestDto requestDto) {\n        return ResponseEntity.created(URI.create(\"/rentals/\" + 1)).body(new RentalResponseDto(1, \"READY\"));\n    }\n}
\n

Rest Docs 문서 설정

\n

Rest Docs를 통해 문서화를 할 경우 다음과 같은 객체를 사용하는 것을 권장합니다, MockMvc / WebTestClient / RestAssured\n각 테스트 객체의 특징을 고려하여 선택한 후 문서화에 사용하면 좋을 것 같습니다.\n상황에 따라서 여러 종류의 테스트 객체를 사용해야 할 수 도 있습니다.

\n

참고 문서

\n\n

TDD 기능 구현

\n

Controller TDD

\n

Controller의 메서드에서 구현할 내용에 대해서 given을 통해 응답을 정의합니다.\n작업 순서는 Controller Test를 작성하면서 Service 클래스를 생성하여 빈 껍데기 메서드만 만든 뒤 Controller 프로덕션 코드를 작성합니다.\nController 테스트에서 MockMvc 객체를 사용한 이유는 Interceptor나 ArgumentResolver와 같이 Controller 이전에 동작하는 기능 등 전체 로직을 통합 테스트하기 위해서 입니다.\n만약 Controller나 Interceptor, ArgumentResolver 와 같은 객체들을 단위테스트 할 경우, 해당 객체를 생성하여 메서드를 바로 호출하는 테스트를 작성해도 무관합니다.\nRentalService의 createRental 메서드에서는 Item조회를 하고 Item이 유효한지 확인한 후 Rental을 만들어 저장하는 로직을 구현할 예정입니다.

\n

아래 코드의 개발 순서는

\n
@WebMvcTest(controllers = RentalController.class)\n@AutoConfigureMockMvc\npublic class RentalControllerTest {\n    @Autowired\n    private MockMvc mockMvc;\n\n    // 3 - RentalService 객체를 만들고 createRental메서드 생성\n    @MockBean\n    private RentalService rentalService;\n\n    @DisplayName(\"대여 신청을 한다.\")\n    @Test\n    public void createRental() throws Exception {\n\n        // 5 - ItemTest와 RentalTest를 생성하고 생성하는 로직을 구현\n        Item item = new Item(110920, \"READY\");\n        Rental rental = new Rental(1, \"20191127\", item, \"READY\");\n\n        // 2 - item 존재 여부확인 + rental 저장 로직을 service 객체로 분리하기로 결정\n        given(rentalService.createRental(any())).willReturn(rental);\n\n        // 1\n        String inputJson = \"{\\\"date\\\":\\\"\" + \"20191127\" + \"\\\", \" +\n                \"\\\"itemId\\\":\\\"\" + \"110920\" + \"\\\"}\";\n        mockMvc.perform(post(\"/rentals\")\n                .contentType(MediaType.APPLICATION_JSON)\n                .accept(MediaType.APPLICATION_JSON)\n                .content(inputJson))\n                .andExpect(status().isCreated())\n                .andExpect(header().exists(\"Location\"))\n                .andExpect(jsonPath(\"$.id\").isNotEmpty())\n                .andExpect(jsonPath(\"$.date\").value(\"20191127\"))\n                .andExpect(jsonPath(\"$.itemId\").value(\"110920\"))\n                .andExpect(jsonPath(\"$.status\").value(\"READY\"))\n                .andDo(print());\n    }\n}\n\n// 4\n@Service\npublic class RentalService {\n    public Rental createRental(RentalRequestDto requestDto) {\n        return null;\n    }\n}\n\n// 6\n@RestController\npublic class RentalController {\n    private RentalService rentalService;\n\n    public RentalController(RentalService rentalService) {\n        this.rentalService = rentalService;\n    }\n\n    @PostMapping(\"/rentals\")\n    public ResponseEntity createArticles(@RequestBody RentalRequestDto requestDto) {\n        Rental persistRental = rentalService.createRental(requestDto);\n        return ResponseEntity.created(URI.create(\"/rentals/\" + persistRental.getId())).body(persistRental);\n    }\n}
\n

Service TDD

\n

Controller Test에서 given으로 정의한 Service 메서드를 구현합니다. Controller와 마찬가지로 테스트를 먼저 작성한 뒤 프로덕션 코드를 작성합니다.\nService 레이어를 테스트 할 때 반드시 Repository를 MockBean으로 간주하여 단위테스트를 해야하는 건 아니며 트랙젝션 등 처리로직 확인을 위해서는 통합테스트로 진행해도 무관합니다.

\n
@SpringBootTest(classes = RentalService.class)\npublic class RentalServiceTest {\n    private RentalService rentalService;\n\n    // 4\n    @MockBean\n    private RentalRepository rentalRepository;\n    @MockBean\n    private ItemRepository itemRepository;\n\n    @BeforeEach\n    void setUp() {\n        rentalService = new RentalService(rentalRepository, itemRepository);\n    }\n\n    @Test\n    void createRental() {\n        Item item = new Item(1, \"READY\");\n\n        // 3\n        given(itemRepository.findById(anyInt())).willReturn(item);\n\n        RentalRequestDto rentalRequestDto = new RentalRequestDto(\"20191127\", 1123);\n\n        // 2\n        given(rentalRepository.save(any())).willReturn(new Rental(1, rentalRequestDto.getDate(), item, \"READY\"));\n\n        // 1\n        Rental persistRental = rentalService.createRental(rentalRequestDto);\n\n        assertThat(persistRental).isNotNull();\n    }\n}\n\n// 5\n@Service\npublic class RentalService {\n    private RentalRepository rentalRepository;\n    private ItemRepository itemRepository;\n\n    public RentalService(RentalRepository rentalRepository, ItemRepository itemRepository) {\n        this.rentalRepository = rentalRepository;\n        this.itemRepository = itemRepository;\n    }\n\n    public Rental createRental(RentalRequestDto requestDto) {\n        Item persistItem = itemRepository.findById(requestDto.getItemId());\n        Rental persistRental = new Rental(requestDto.getDate(), persistItem, \"READY\");\n        return rentalRepository.save(persistRental);\n    }\n}
\n

Repository TDD

\n
@SpringBootTest(classes = RentalRepository.class)\npublic class RentalRepositoryTest {\n    private RentalRepository rentalRepository;\n\n    @BeforeEach\n    void setUp() {\n        rentalRepository = new RentalRepository();\n    }\n\n    @Test\n    void save() {\n        // 2\n        Item item = new Item(110920, \"READY\");\n        Rental rental = new Rental(\"20191127\", item, \"READY\");\n\n        // 1\n        Rental persistRental = rentalRepository.save(rental);\n\n        assertThat(persistRental.getId()).isEqualTo(1);\n    }\n}\n\n@Repository\npublic class RentalRepository {\n    private List<Rental> rentals = new ArrayList<>();\n\n    public Rental save(Rental rental) {\n        Rental persistRental = new Rental(rentals.size() + 1, rental.getDate(), rental.getItem(), rental.getStatus());\n        rentals.add(persistRental);\n        return persistRental;\n    }\n}
\n
// ItemRepository도 같은 방식으로 진행
\n

Controller TDD - 예외 케이스

\n

정상적인 로직에 대한 테스트 작성이 끝나면 Side Case에 대한 테스트를 작성해야합니다.\nHappy Case에 대한 테스트를 작성하는 것 처럼 테스트 케이스에 대한 메서드를 만들어주고 given 조건과 기대하는 then 조건을 설정합니다.

\n

참고로 도메인 클래스 작성은 어느 레이어에서 해도 상관이 없다고 생각합니다.\nOutside in 이라고 해서 반드시 그 방향대로(ex. Controller -> Service -> Repository 순) 개발을 해야하는 것이 아니라\n테스트를 구현하고 로직을 구현하다가 필요한 로직이 생기면 그 때 해당 부분을 구현하기 위해 테스트 코드를 구현하고 로직을 구현하였습니다.

\n
@DisplayName(\"대여 신청 시 아이템이 이미 대여중인 경우 400에러를 응답한다.\")\n@Test\npublic void createRentalWithInvalidItemId() throws Exception {\n    given(rentalService.createRental(any())).willThrow(new InvalidItemException());\n\n    String inputJson = \"{\\\"date\\\":\\\"\" + \"20191127\" + \"\\\", \" +\n            \"\\\"itemId\\\":\\\"\" + \"111\" + \"\\\"}\";\n\n    mockMvc.perform(post(\"/rentals\")\n            .contentType(MediaType.APPLICATION_JSON)\n            .accept(MediaType.APPLICATION_JSON)\n            .content(inputJson))\n            .andExpect(status().isBadRequest())\n            .andDo(print());\n}\n\n@ResponseStatus(HttpStatus.BAD_REQUEST)\npublic class InvalidItemException extends RuntimeException{\n}\n\npublic class RentalTest {\n    @Test()\n    void checkItem() {\n        Assertions.assertThrows(AlreadyRentItemException.class, () -> {\n            new Rental(\"20191127\", new Item(111, \"RENT\"), \"READY\");\n        });\n    }\n}\n\npublic class Rental {\n    ...\n\n    public Rental(String date, Item item, String status) {\n        item.checkStatus();\n        this.date = date;\n        this.item = item;\n        this.status = status;\n    }\n\n    ...\n}\n\npublic class ItemTest {\n    @Test\n    void checkStatus() {\n        Item item = new Item(111, \"RENT\");\n        Assertions.assertThrows(AlreadyRentItemException.class, () -> {\n            item.checkStatus();\n        });\n    }\n}\n\n\npublic class Item {\n    ...\n\n    public void checkStatus() {\n        if (status.equals(\"RENT\")) {\n            throw new AlreadyRentItemException();\n        }\n    }\n}
\n

리팩터링

\n

인수 테스트 동작 확인

\n

TDD 프로세스를 통해 기능 구현이 끝나면 최초 작성한 인수 테스트가 정상적으로 동작하는지 확인을 합니다.\n최초에 인수 테스트가 성공한 것은 Mock 서버가 동작하고 있었기 때문인데 이 기능을 실제 기능 구현으로 대체하게 되었습니다.

","excerpt":"TDD를 배우면서 도메인에 먼저 집중하여 도메인 기능의 테스트 코드를 구현한 후 기능을 구현하는 방법으로 학습합니다.\n한 번에 한 가지를 집중하고 각 도메인들은 애플리케이션이 구성되기 전에 작성됩니다.\n하지만 ATDD…"}},"pageContext":{"type":"posts","next":{"frontmatter":{"path":"/1","title":"ATDD 프로세스","tags":["ATDD","Agile"]},"fileAbsolutePath":"/Users/brown/workspaces/blog/boorownie.github.io/src/posts/1.md"},"previous":{"frontmatter":{"path":"/3","title":"가볍게 시작하는 인수 테스트 주도 개발","tags":["ATDD","TDD","BDD"]},"fileAbsolutePath":"/Users/brown/workspaces/blog/boorownie.github.io/src/posts/3.md"}}},"staticQueryHashes":["1425477374","3128451518"]} \ No newline at end of file diff --git a/page-data/3/page-data.json b/page-data/3/page-data.json new file mode 100644 index 0000000..a2d2bde --- /dev/null +++ b/page-data/3/page-data.json @@ -0,0 +1 @@ +{"componentChunkName":"component---src-templates-page-js","path":"/3","result":{"data":{"markdownRemark":{"frontmatter":{"title":"가볍게 시작하는 인수 테스트 주도 개발","date":"20 November 2020","path":"/3","author":"Boorownie","excerpt":null,"tags":["ATDD","TDD","BDD"],"series":[{"title":"1. 가볍게 시작하는 인수 테스트 주도 개발","path":"/3"},{"title":"2. 인수 테스트 주도로 개발해보기 with Spring","path":null},{"title":"3. 인수 테스트 주도 개발 팁","path":null}],"coverImage":null},"id":"8f7c029e-1df4-5649-94de-dc6b9a4710b0","html":"

이번 시리즈에서는 인수 테스트 주도 개발을 개발 관점으로 접근하여 용어 정리를 하고 예시를 통해 적용하는 방법을 알아보겠습니다.

\n

TDD

\n

많은 분들이 아시다시피 TDD 사이클은 3단계로 이루어져있습니다.\n\n \n \n \n \n

\n
    \n
  • 실패하는 테스트 작성하기
  • \n
  • 테스트를 성공시키기
  • \n
  • 리팩터링 하기
  • \n
\n

TDD 사이클에서 크게 두 가지 중요한 점은 테스트 코드 우선과 리팩터링입니다.\n하나는 요구사항을 검증하는 테스트 코드를 먼저 만들어 구현하고자 하는 것을 조금 더 명확히 하는 것이고, 다른 하나는 구현 후 리팩터링을 진행하여 다음 사이클을 대비하고 유지 보수하기 좋은 코드로 유지하게끔 하는 작업을 뒤로 미루지 않게 도와줍니다.\n리팩터링에 대해서는 추후 포스팅에서 다루기로 하고 이번에는 테스트 우선에 대해서 조금 더 깊게 이야기해보겠습니다.

\n

ATDD - 인수 테스트 주도 개발

\n

ATDD는 시나리오 레벨의 구현하고자 하는 대상을 인수 테스트로 정하고 개발을 시작합니다.\n대상을 테스트로 정하고 개발에 들어가는 TDD 사이클과 유사하다고 할 수 있습니다.\n여기서 인수 테스트는 작은 단위의 모듈이 아닌 기능 시나리오 레벨의 검증을 목적으로 합니다.

\n

\n \n \n \n \n

\n

인수 테스트 정의가 끝나면, 이 인수 테스트를 성공하게 하기 위해 작은 단위의 TDD 사이클을 반복합니다.\n인수 테스트가 성공할 때까지 여러 번의 TDD 사이클이 반복되어 수행되고, 인수 테스트가 성공하면 리팩터링 단계를 거친 후 ATDD 사이클이 끝납니다.

\n

TDD와 ATDD 비교

\n

엄밀히 따지면 ATDD는 TDD의 여러 종류 중 하나라고 할 수 있습니다. TDD의 T(Test)를 AT(Acceptance Test)로 한정하여 진행합니다.\n그러면 굳이 왜 인수 테스트라고 한정 지을까요?\n앞서 이야기한 대로 테스트를 인수 테스트로 한정 지으면 테스트로 검증하고자 하는 대상이 작은 단위가 아니라 시나리오 레벨로 정할 수 있습니다.

\n

시나리오 레벨의 검증

\n

기존 TDD는 작은 단위에 대한 검증에는 뛰어나지만, 각각을 구현한 후 전체 영역이 잘 동작하는지 확인할 때 의도와 다른 경우가 종종 있습니다.\n이때 전체 영역을 커버할 수 있는 인수 테스트를 먼저 작성하고 이 인수 테스트를 성공시키기 위해 TDD 사이클을 반복한다면 뚜렷한 방향성을 가지고 개발을 진행할 수 있습니다.

\n

자연스럽게 다음 TDD 사이클로 이어지기

\n

TDD를 이용하여 코드를 작성해 나갈 때 TDD 사이클을 유지하는 게 생각보다 어렵습니다.\n테스트 작성 후 기능 구현하고 리팩터링을 한 뒤 다음을 어떤 걸 구현해야 할지 막막했던 경험이 생각이 나네요.\n생각해보면 TDD는 단순한 규칙을 가지고 있습니다. 그래서 그런지 작은 단위의 기능을 구현하다 보면 내가 뭐를 구현하기 위해서 이 기능을 만들고 있지? 라는 생각이 들 때도 있습니다.\n인수 테스트를 먼저 작성하여 전체적인 기능과 시나리오에 대한 이해를 명시적으로 선언한 뒤 개발을 진행한다면, 이후 한참 개발을 진행하다가도 이 인수 테스트를 통해 다시 한번 작업의 방향성을 확인할 수 있습니다.

\n

인수 테스트, 너무 번거로운 거 아닌가?

\n

인수 테스트(Acceptance Test)는 사용자 인수 테스트(User Acceptance Test)로 많이 불립니다.\n사용자 인수 테스트는 말 그대로 요구사항을 사용자가 직접 검증하여 개발이 완료되었음을 증명하는 테스트입니다.\n사용자가 직접 검증하기 위해서는 내부적인 코드를 활용할 수 없고 실제 서비스를 사용하는 방법과 유사하게 테스트가 진행되어야 합니다.\n구체적인 기술(개발 방법)을 몰라도 요구사항 베이스에서 진행할 수 있어야 하죠.\n예를 들면 브라우저나 디바이스를 통해 사용자의 Interaction(UI)부터 시작하여 전체 사이클을 검증할 수 있어야 합니다.\n즉, UI부터 전체 기능이 정상적으로 동작하는지 확인할 수 있어야 합니다.

\n

사용자 인수 테스트를 자동화하려면 (엄청나게) 큰 노력과 리소스가 필요할 수 있습니다.\n그 테스트의 효과는 좋을 수 있겠지만, 테스트를 구성하고 유지보수를 하려면 그만큼 TDD의 허들이 높아집니다.

\n

테스트 시나리오의 품질 관리

\n

구체적인 행위를 검증하기보다는 비즈니스 규칙을 검증해야 테스트 시나리오의 품질을 관리하는 데 유리합니다.\n상대적으로 비즈니스 규칙보다는 구체적인 행위의 변경이 빈번하게 일어나기 때문이죠.

\n
\n

\n
\n

백엔드 개발 시 앞서 이야기한 방법처럼 UI 기반 인수 테스트를 작성하기 위해서는 구체적인 행위를 검증할 수 있어야 합니다.\n따라서 UI를 포함한 전체 레벨의 테스트를 하는 것은 큰 부담입니다.

\n

ex) selenium을 활용한 UI 테스트 예시

\n
import org.openqa.selenium.By;\nimport org.openqa.selenium.Keys;\nimport org.openqa.selenium.WebDriver;\nimport org.openqa.selenium.WebElement;\nimport org.openqa.selenium.firefox.FirefoxDriver;\nimport org.openqa.selenium.support.ui.WebDriverWait;\nimport static org.openqa.selenium.support.ui.ExpectedConditions.presenceOfElementLocated;\nimport java.time.Duration;\npublic class HelloSelenium {\n    public static void main(String[] args) {\n        WebDriver driver = new FirefoxDriver();\n        WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));\n        try {\n            driver.get(\"https://google.com/ncr\");\n            driver.findElement(By.name(\"q\")).sendKeys(\"cheese\" + Keys.ENTER);\n            WebElement firstResult = wait.until(presenceOfElementLocated(By.cssSelector(\"h3>div\")));\n            System.out.println(firstResult.getAttribute(\"textContent\"));\n        } finally {\n            driver.quit();\n        }\n    }\n}
\n

API 레벨 인수 테스트

\n

아무리 좋은 방법이라 하더라도 실무에 적용하기 힘들정도로 번거롭고 품질관리하기 어려우면 현실적으로 사용하기가 어렵습니다.\n실무에서 ATDD를 통해 개발하기 위해서는 최대한 허들을 낮추어 많이 활용될 수 있도록 노력해야 합니다.

\n

저는 UI 레벨에서의 인수 테스트가 아닌 API 레벨로 인수 테스트를 하는 것을 추천합니다.\n인수 테스트를 위한 시나리오를 작성하고 이를 검증하는 인수 테스트를 API 레벨에서 만든 뒤 이를 구현하는 방법으로 진행하면 조금 수월하게 ATDD 사이클을 유지할 수 있습니다.

\n

다음 포스팅에서 API 레벨의 인수 테스트 주도 개발 예시를 다루어보겠습니다.

","excerpt":"이번 시리즈에서는 인수 테스트 주도 개발을 으로 접근하여 용어 정리를 하고 예시를 통해 적용하는 방법을 알아보겠습니다. TDD 많은 분들이 아시다시피 TDD 사이클은…"}},"pageContext":{"type":"posts","next":{"frontmatter":{"path":"/2","title":"ATDD with Spring Boot","tags":["ATDD","Spring","Spring Boot"]},"fileAbsolutePath":"/Users/brown/workspaces/blog/boorownie.github.io/src/posts/2.md"},"previous":null}},"staticQueryHashes":["1425477374","3128451518"]} \ No newline at end of file diff --git a/page-data/404.html/page-data.json b/page-data/404.html/page-data.json new file mode 100644 index 0000000..6073037 --- /dev/null +++ b/page-data/404.html/page-data.json @@ -0,0 +1 @@ +{"componentChunkName":"component---src-pages-404-js","path":"/404.html","result":{"pageContext":{}},"staticQueryHashes":["1425477374","3128451518"]} \ No newline at end of file diff --git a/page-data/404/page-data.json b/page-data/404/page-data.json new file mode 100644 index 0000000..8960438 --- /dev/null +++ b/page-data/404/page-data.json @@ -0,0 +1 @@ +{"componentChunkName":"component---src-pages-404-js","path":"/404/","result":{"pageContext":{}},"staticQueryHashes":["1425477374","3128451518"]} \ No newline at end of file diff --git a/page-data/about/page-data.json b/page-data/about/page-data.json new file mode 100644 index 0000000..20f00bc --- /dev/null +++ b/page-data/about/page-data.json @@ -0,0 +1 @@ +{"componentChunkName":"component---src-templates-page-js","path":"/about","result":{"data":{"markdownRemark":{"frontmatter":{"title":"About","date":"19 July 2018","path":"/about","author":"Radek","excerpt":null,"tags":null,"series":null,"coverImage":null},"id":"fcc2e5b8-dbdd-5b90-a275-b137eb90c8ad","html":"

Hi there

\n

My name is Radek and I'm the author of this starter. I made it to help you present your ideas easier.

\n

We all know how hard is to start something on the web, especially these days. You need to prepare a bunch of stuff, configure them and when that’s done — create the content.

\n

This starter is pretty basic and covers all of the essentials. All you have to do is start typing!

\n

The starter includes:

\n\n

So, there you have it... enjoy!

","excerpt":"Hi there My name is Radek and I'm the author of this starter. I made it to help you present your ideas easier. We all know how hard is to…"}},"pageContext":{"type":"pages","next":{"frontmatter":{"path":"/showcase","title":"Showcase","tags":null},"fileAbsolutePath":"/Users/brown/workspaces/blog/boorownie.github.io/src/pages/showcase.md"},"previous":null}},"staticQueryHashes":["1425477374","3128451518"]} \ No newline at end of file diff --git a/page-data/app-data.json b/page-data/app-data.json new file mode 100644 index 0000000..94de8b2 --- /dev/null +++ b/page-data/app-data.json @@ -0,0 +1 @@ +{"webpackCompilationHash":"7f134aea7ea48b59cfc5"} diff --git a/page-data/index/page-data.json b/page-data/index/page-data.json new file mode 100644 index 0000000..b85bdd1 --- /dev/null +++ b/page-data/index/page-data.json @@ -0,0 +1 @@ +{"componentChunkName":"component---src-templates-index-js","path":"/","result":{"data":{"allMarkdownRemark":{"edges":[{"node":{"id":"8f7c029e-1df4-5649-94de-dc6b9a4710b0","excerpt":"이번 시리즈에서는 인수 테스트 주도 개발을 으로 접근하여 용어 정리를 하고 예시를 통해 적용하는 방법을 알아보겠습니다. TDD 많은 분들이 아시다시피 TDD 사이클은…","frontmatter":{"title":"가볍게 시작하는 인수 테스트 주도 개발","date":"20 November 2020","path":"/3","author":"Boorownie","excerpt":null,"tags":["ATDD","TDD","BDD"],"series":[{"title":"1. 가볍게 시작하는 인수 테스트 주도 개발","path":"/3"},{"title":"2. 인수 테스트 주도로 개발해보기 with Spring","path":null},{"title":"3. 인수 테스트 주도 개발 팁","path":null}],"coverImage":null}}},{"node":{"id":"13f27efa-7fb7-5520-816d-784dfe9daae2","excerpt":"TDD를 배우면서 도메인에 먼저 집중하여 도메인 기능의 테스트 코드를 구현한 후 기능을 구현하는 방법으로 학습합니다.\n한 번에 한 가지를 집중하고 각 도메인들은 애플리케이션이 구성되기 전에 작성됩니다.\n하지만 ATDD…","frontmatter":{"title":"ATDD with Spring Boot","date":"30 November 2019","path":"/2","author":"Boorownie","excerpt":null,"tags":["ATDD","Spring","Spring Boot"],"series":null,"coverImage":null}}},{"node":{"id":"ae02313e-fb02-5450-8223-74c275b8e87a","excerpt":"ATDD(Acceptance Test Driven Development…","frontmatter":{"title":"ATDD 프로세스","date":"29 November 2019","path":"/1","author":"Boorownie","excerpt":null,"tags":["ATDD","Agile"],"series":null,"coverImage":null}}}]}},"pageContext":{"pageNumber":0,"humanPageNumber":1,"skip":0,"limit":5,"numberOfPages":1,"previousPagePath":"","nextPagePath":""}},"staticQueryHashes":["1425477374","3128451518"]} \ No newline at end of file diff --git a/page-data/showcase/page-data.json b/page-data/showcase/page-data.json new file mode 100644 index 0000000..2bce40b --- /dev/null +++ b/page-data/showcase/page-data.json @@ -0,0 +1 @@ +{"componentChunkName":"component---src-templates-page-js","path":"/showcase","result":{"data":{"markdownRemark":{"frontmatter":{"title":"Showcase","date":"17 July 2018","path":"/showcase","author":"Hello Robot","excerpt":null,"tags":null,"series":null,"coverImage":null},"id":"c35b507f-9958-5f4d-bada-7f3878aba786","html":"

Header 2

\n

Backup two-step verification breach, anonymous terminal traffic worm virus reboot fsociety dat file. Traffic fsociety malware 100 terabytes system hack, delete brute-force cyber security fiber connection connect code worm wipe. Cyber security off the grid delete IP decrypt, nodes connect password 100 terabytes RUDY attack malicious code rootkit gigabit speed. Tor connect network, intercepting traffic off the grid IP protocol password.

\n
\n

Backup DDoS attack rootkit nodes disconnect website. Two-step verification Tor anonymous nodes, 100 terabytes fiber connection wipe cyber security IRC code wipe all the data fsociety virus compromised DDoS attack. Sys admin data center gigabit speed breach, worm DDoS attack AFK nodes.

\n
\n

Header 3

\n

Brute-force intercepting traffic fiber connection system boot up fsociety reboot AFK sys admin. Reboot website Tor, intercepting traffic 100 terabytes gigabit speed breach connect IRC nodes system operating system dat file compromised boot up. Data center decrypt password network disconnect. Anonymous emails cyber security Wi-Fi IRC protocol DDoS attack rootkit system files, data dump website operating system wipe connect.

\n
/* PostCSS code by PrismJS */\n\npre {\n  background: #1a1a1d;\n  padding: 20px;\n  border-radius: 8px;\n  font-size: 1rem;\n  overflow: auto;\n\n  @media (--phone) {\n    white-space: pre-wrap;\n    word-wrap: break-word;\n  }\n\n  code {\n    background: none !important;\n    color: #ccc;\n    padding: 0;\n    font-size: inherit;\n\n    .dark-theme & {\n      color: inherit;\n    }\n  }\n}
\n
// JS code by PrismJS\n\nconst menuTrigger = document.querySelector('.menu-trigger')\nconst menu = document.querySelector('.menu')\nconst mobileQuery = getComputedStyle(document.body).getPropertyValue('--phoneWidth')\nconst isMobile = () => window.matchMedia(mobileQuery).matches\nconst isMobileMenu = () => {\n  menuTrigger.classList.toggle('hidden', !isMobile())\n  menu.classList.toggle('hidden', isMobile())\n}\n\nisMobileMenu()\n\nmenuTrigger.addEventListener('click', () => menu.classList.toggle('hidden'))\n\nwindow.addEventListener('resize', isMobileMenu)
\n
<section id=\"main\">\n  <div>\n   <h1 id=\"title\">{{ .Title }}</h1>\n    {{ range .Pages }}\n        {{ .Render \"summary\"}}\n    {{ end }}\n  </div>\n</section>
\n

Header 4

\n

Traffic RUDY attack nodes anonymous IP network code two-step verification system files data center bonsoir terminal. Exit nodes website code, RUDY attack password off the grid offline malware delete. Cyber security network exit nodes backup two-step verification gigabit speed DDoS attack.

\n
    \n
  • Fsociety delete malicious code nodes.
  • \n
  • IP cyber security wipe all the data sys admin virus compromised dat file malicious code computer.
  • \n
  • Decrypt two-step verification Tor wipe, password cyber security data dump malicious code dat file routing protocol operating system.
  • \n
  • \n

    Anonymous boot up website AFK.

    \n
      \n
    • Timing out IP DNS, log file offline terminal brute-force system files connect server farm.
    • \n
    • Reboot sys admin worm log file wipe.
    • \n
    \n
  • \n
\n

\n

\n \n

\n

Tor boot up backup anonymous bonsoir IRC website. Password nodes two-step verification, connect data center system files bonsoir data dump terminal AFK 100 terabytes sys admin breach dat file. Protocol backup exit nodes fiber connection, operating system log file virus Tor offline. Password data center two-step verification disconnect IRC terminal. Tor IRC cyber security AFK protocol traffic disconnect. Code exit nodes IRC cyber security nodes worm.

","excerpt":"Header 2 Backup two-step verification breach, anonymous terminal traffic worm virus reboot fsociety dat file. Traffic fsociety malware 10…"}},"pageContext":{"type":"pages","next":null,"previous":{"frontmatter":{"path":"/about","title":"About","tags":null},"fileAbsolutePath":"/Users/brown/workspaces/blog/boorownie.github.io/src/pages/about.md"}}},"staticQueryHashes":["1425477374","3128451518"]} \ No newline at end of file diff --git a/page-data/sq/d/1425477374.json b/page-data/sq/d/1425477374.json new file mode 100644 index 0000000..98cba7b --- /dev/null +++ b/page-data/sq/d/1425477374.json @@ -0,0 +1 @@ +{"data":{"site":{"siteMetadata":{"title":"맨땅에 코딩","logo":{"src":"","alt":""},"logoText":"맨땅에 코딩","defaultTheme":"dark","copyrights":"","mainMenu":[{"title":"Gitgub","path":"https://github.com/boorownie"}],"showMenuItems":2,"menuMoreText":"Show more"}}}} \ No newline at end of file diff --git a/page-data/sq/d/3128451518.json b/page-data/sq/d/3128451518.json new file mode 100644 index 0000000..02a89f8 --- /dev/null +++ b/page-data/sq/d/3128451518.json @@ -0,0 +1 @@ +{"data":{"site":{"siteMetadata":{"title":"맨땅에 코딩","description":"boorownie's blog","author":"Boorownie"}}}} \ No newline at end of file diff --git a/page-data/sq/d/753231593.json b/page-data/sq/d/753231593.json new file mode 100644 index 0000000..0dad385 --- /dev/null +++ b/page-data/sq/d/753231593.json @@ -0,0 +1 @@ +{"data":{"placeholderImage":null}} \ No newline at end of file diff --git a/page-data/tag/ATDD/page-data.json b/page-data/tag/ATDD/page-data.json new file mode 100644 index 0000000..65fd68c --- /dev/null +++ b/page-data/tag/ATDD/page-data.json @@ -0,0 +1 @@ +{"componentChunkName":"component---src-templates-tags-js","path":"/tag/ATDD","result":{"data":{"allMarkdownRemark":{"edges":[{"node":{"id":"8f7c029e-1df4-5649-94de-dc6b9a4710b0","excerpt":"이번 시리즈에서는 인수 테스트 주도 개발을 으로 접근하여 용어 정리를 하고 예시를 통해 적용하는 방법을 알아보겠습니다. TDD 많은 분들이 아시다시피 TDD 사이클은…","frontmatter":{"title":"가볍게 시작하는 인수 테스트 주도 개발","date":"20 November 2020","path":"/3","author":"Boorownie","excerpt":null,"tags":["ATDD","TDD","BDD"],"series":[{"title":"1. 가볍게 시작하는 인수 테스트 주도 개발","path":"/3"},{"title":"2. 인수 테스트 주도로 개발해보기 with Spring","path":null},{"title":"3. 인수 테스트 주도 개발 팁","path":null}],"coverImage":null}}},{"node":{"id":"13f27efa-7fb7-5520-816d-784dfe9daae2","excerpt":"TDD를 배우면서 도메인에 먼저 집중하여 도메인 기능의 테스트 코드를 구현한 후 기능을 구현하는 방법으로 학습합니다.\n한 번에 한 가지를 집중하고 각 도메인들은 애플리케이션이 구성되기 전에 작성됩니다.\n하지만 ATDD…","frontmatter":{"title":"ATDD with Spring Boot","date":"30 November 2019","path":"/2","author":"Boorownie","excerpt":null,"tags":["ATDD","Spring","Spring Boot"],"series":null,"coverImage":null}}},{"node":{"id":"ae02313e-fb02-5450-8223-74c275b8e87a","excerpt":"ATDD(Acceptance Test Driven Development…","frontmatter":{"title":"ATDD 프로세스","date":"29 November 2019","path":"/1","author":"Boorownie","excerpt":null,"tags":["ATDD","Agile"],"series":null,"coverImage":null}}}]}},"pageContext":{"tag":"ATDD","pageNumber":0,"humanPageNumber":1,"skip":0,"limit":5,"numberOfPages":1,"previousPagePath":"","nextPagePath":""}},"staticQueryHashes":["1425477374","3128451518"]} \ No newline at end of file diff --git a/page-data/tag/Agile/page-data.json b/page-data/tag/Agile/page-data.json new file mode 100644 index 0000000..4636c21 --- /dev/null +++ b/page-data/tag/Agile/page-data.json @@ -0,0 +1 @@ +{"componentChunkName":"component---src-templates-tags-js","path":"/tag/Agile","result":{"data":{"allMarkdownRemark":{"edges":[{"node":{"id":"ae02313e-fb02-5450-8223-74c275b8e87a","excerpt":"ATDD(Acceptance Test Driven Development…","frontmatter":{"title":"ATDD 프로세스","date":"29 November 2019","path":"/1","author":"Boorownie","excerpt":null,"tags":["ATDD","Agile"],"series":null,"coverImage":null}}}]}},"pageContext":{"tag":"Agile","pageNumber":0,"humanPageNumber":1,"skip":0,"limit":5,"numberOfPages":1,"previousPagePath":"","nextPagePath":""}},"staticQueryHashes":["1425477374","3128451518"]} \ No newline at end of file diff --git a/page-data/tag/BDD/page-data.json b/page-data/tag/BDD/page-data.json new file mode 100644 index 0000000..b9dd6fb --- /dev/null +++ b/page-data/tag/BDD/page-data.json @@ -0,0 +1 @@ +{"componentChunkName":"component---src-templates-tags-js","path":"/tag/BDD","result":{"data":{"allMarkdownRemark":{"edges":[{"node":{"id":"8f7c029e-1df4-5649-94de-dc6b9a4710b0","excerpt":"이번 시리즈에서는 인수 테스트 주도 개발을 으로 접근하여 용어 정리를 하고 예시를 통해 적용하는 방법을 알아보겠습니다. TDD 많은 분들이 아시다시피 TDD 사이클은…","frontmatter":{"title":"가볍게 시작하는 인수 테스트 주도 개발","date":"20 November 2020","path":"/3","author":"Boorownie","excerpt":null,"tags":["ATDD","TDD","BDD"],"series":[{"title":"1. 가볍게 시작하는 인수 테스트 주도 개발","path":"/3"},{"title":"2. 인수 테스트 주도로 개발해보기 with Spring","path":null},{"title":"3. 인수 테스트 주도 개발 팁","path":null}],"coverImage":null}}}]}},"pageContext":{"tag":"BDD","pageNumber":0,"humanPageNumber":1,"skip":0,"limit":5,"numberOfPages":1,"previousPagePath":"","nextPagePath":""}},"staticQueryHashes":["1425477374","3128451518"]} \ No newline at end of file diff --git a/page-data/tag/Spring-Boot/page-data.json b/page-data/tag/Spring-Boot/page-data.json new file mode 100644 index 0000000..8e161ec --- /dev/null +++ b/page-data/tag/Spring-Boot/page-data.json @@ -0,0 +1 @@ +{"componentChunkName":"component---src-templates-tags-js","path":"/tag/Spring-Boot","result":{"data":{"allMarkdownRemark":{"edges":[{"node":{"id":"13f27efa-7fb7-5520-816d-784dfe9daae2","excerpt":"TDD를 배우면서 도메인에 먼저 집중하여 도메인 기능의 테스트 코드를 구현한 후 기능을 구현하는 방법으로 학습합니다.\n한 번에 한 가지를 집중하고 각 도메인들은 애플리케이션이 구성되기 전에 작성됩니다.\n하지만 ATDD…","frontmatter":{"title":"ATDD with Spring Boot","date":"30 November 2019","path":"/2","author":"Boorownie","excerpt":null,"tags":["ATDD","Spring","Spring Boot"],"series":null,"coverImage":null}}}]}},"pageContext":{"tag":"Spring Boot","pageNumber":0,"humanPageNumber":1,"skip":0,"limit":5,"numberOfPages":1,"previousPagePath":"","nextPagePath":""}},"staticQueryHashes":["1425477374","3128451518"]} \ No newline at end of file diff --git a/page-data/tag/Spring/page-data.json b/page-data/tag/Spring/page-data.json new file mode 100644 index 0000000..5be1a3c --- /dev/null +++ b/page-data/tag/Spring/page-data.json @@ -0,0 +1 @@ +{"componentChunkName":"component---src-templates-tags-js","path":"/tag/Spring","result":{"data":{"allMarkdownRemark":{"edges":[{"node":{"id":"13f27efa-7fb7-5520-816d-784dfe9daae2","excerpt":"TDD를 배우면서 도메인에 먼저 집중하여 도메인 기능의 테스트 코드를 구현한 후 기능을 구현하는 방법으로 학습합니다.\n한 번에 한 가지를 집중하고 각 도메인들은 애플리케이션이 구성되기 전에 작성됩니다.\n하지만 ATDD…","frontmatter":{"title":"ATDD with Spring Boot","date":"30 November 2019","path":"/2","author":"Boorownie","excerpt":null,"tags":["ATDD","Spring","Spring Boot"],"series":null,"coverImage":null}}}]}},"pageContext":{"tag":"Spring","pageNumber":0,"humanPageNumber":1,"skip":0,"limit":5,"numberOfPages":1,"previousPagePath":"","nextPagePath":""}},"staticQueryHashes":["1425477374","3128451518"]} \ No newline at end of file diff --git a/page-data/tag/TDD/page-data.json b/page-data/tag/TDD/page-data.json new file mode 100644 index 0000000..1eb9c10 --- /dev/null +++ b/page-data/tag/TDD/page-data.json @@ -0,0 +1 @@ +{"componentChunkName":"component---src-templates-tags-js","path":"/tag/TDD","result":{"data":{"allMarkdownRemark":{"edges":[{"node":{"id":"8f7c029e-1df4-5649-94de-dc6b9a4710b0","excerpt":"이번 시리즈에서는 인수 테스트 주도 개발을 으로 접근하여 용어 정리를 하고 예시를 통해 적용하는 방법을 알아보겠습니다. TDD 많은 분들이 아시다시피 TDD 사이클은…","frontmatter":{"title":"가볍게 시작하는 인수 테스트 주도 개발","date":"20 November 2020","path":"/3","author":"Boorownie","excerpt":null,"tags":["ATDD","TDD","BDD"],"series":[{"title":"1. 가볍게 시작하는 인수 테스트 주도 개발","path":"/3"},{"title":"2. 인수 테스트 주도로 개발해보기 with Spring","path":null},{"title":"3. 인수 테스트 주도 개발 팁","path":null}],"coverImage":null}}}]}},"pageContext":{"tag":"TDD","pageNumber":0,"humanPageNumber":1,"skip":0,"limit":5,"numberOfPages":1,"previousPagePath":"","nextPagePath":""}},"staticQueryHashes":["1425477374","3128451518"]} \ No newline at end of file diff --git a/polyfill-303683f6c6a5fb752dca.js b/polyfill-303683f6c6a5fb752dca.js new file mode 100644 index 0000000..88af465 --- /dev/null +++ b/polyfill-303683f6c6a5fb752dca.js @@ -0,0 +1,2 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[10],{VXT5:function(t,e,r){(function(t){!function(){var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:void 0!==t?t:"undefined"!=typeof self?self:{};function r(t,e,r){return t(r={path:e,exports:{},require:function(t,e){return function(){throw new Error("Dynamic requires are not currently supported by @rollup/plugin-commonjs")}()}},r.exports),r.exports}var n=function(t){return t&&t.Math==Math&&t},o=n("object"==typeof globalThis&&globalThis)||n("object"==typeof window&&window)||n("object"==typeof self&&self)||n("object"==typeof e&&e)||function(){return this}()||Function("return this")(),i=function(t){try{return!!t()}catch(t){return!0}},a=!i((function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]})),u={}.propertyIsEnumerable,c=Object.getOwnPropertyDescriptor,s={f:c&&!u.call({1:2},1)?function(t){var e=c(this,t);return!!e&&e.enumerable}:u},f=function(t,e){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:e}},l={}.toString,h=function(t){return l.call(t).slice(8,-1)},p="".split,d=i((function(){return!Object("z").propertyIsEnumerable(0)}))?function(t){return"String"==h(t)?p.call(t,""):Object(t)}:Object,v=function(t){if(null==t)throw TypeError("Can't call method on "+t);return t},g=function(t){return d(v(t))},y=function(t){return"object"==typeof t?null!==t:"function"==typeof t},m=function(t,e){if(!y(t))return t;var r,n;if(e&&"function"==typeof(r=t.toString)&&!y(n=r.call(t)))return n;if("function"==typeof(r=t.valueOf)&&!y(n=r.call(t)))return n;if(!e&&"function"==typeof(r=t.toString)&&!y(n=r.call(t)))return n;throw TypeError("Can't convert object to primitive value")},b={}.hasOwnProperty,S=function(t,e){return b.call(t,e)},E=o.document,w=y(E)&&y(E.createElement),R=function(t){return w?E.createElement(t):{}},T=!a&&!i((function(){return 7!=Object.defineProperty(R("div"),"a",{get:function(){return 7}}).a})),O=Object.getOwnPropertyDescriptor,x={f:a?O:function(t,e){if(t=g(t),e=m(e,!0),T)try{return O(t,e)}catch(t){}if(S(t,e))return f(!s.f.call(t,e),t[e])}},A=function(t){if(!y(t))throw TypeError(String(t)+" is not an object");return t},I=Object.defineProperty,_={f:a?I:function(t,e,r){if(A(t),e=m(e,!0),A(r),T)try{return I(t,e,r)}catch(t){}if("get"in r||"set"in r)throw TypeError("Accessors not supported");return"value"in r&&(t[e]=r.value),t}},j=a?function(t,e,r){return _.f(t,e,f(1,r))}:function(t,e,r){return t[e]=r,t},P=function(t,e){try{j(o,t,e)}catch(n){o[t]=e}return e},N=o["__core-js_shared__"]||P("__core-js_shared__",{}),M=Function.toString;"function"!=typeof N.inspectSource&&(N.inspectSource=function(t){return M.call(t)});var U,k,L,D=N.inspectSource,C=o.WeakMap,F="function"==typeof C&&/native code/.test(D(C)),B=r((function(t){(t.exports=function(t,e){return N[t]||(N[t]=void 0!==e?e:{})})("versions",[]).push({version:"3.7.0",mode:"global",copyright:"© 2020 Denis Pushkarev (zloirock.ru)"})})),W=0,z=Math.random(),G=function(t){return"Symbol("+String(void 0===t?"":t)+")_"+(++W+z).toString(36)},K=B("keys"),V=function(t){return K[t]||(K[t]=G(t))},$={};if(F){var q=N.state||(N.state=new(0,o.WeakMap)),H=q.get,X=q.has,Y=q.set;U=function(t,e){return e.facade=t,Y.call(q,t,e),e},k=function(t){return H.call(q,t)||{}},L=function(t){return X.call(q,t)}}else{var J=V("state");$[J]=!0,U=function(t,e){return e.facade=t,j(t,J,e),e},k=function(t){return S(t,J)?t[J]:{}},L=function(t){return S(t,J)}}var Q,Z={set:U,get:k,has:L,enforce:function(t){return L(t)?k(t):U(t,{})},getterFor:function(t){return function(e){var r;if(!y(e)||(r=k(e)).type!==t)throw TypeError("Incompatible receiver, "+t+" required");return r}}},tt=r((function(t){var e=Z.get,r=Z.enforce,n=String(String).split("String");(t.exports=function(t,e,i,a){var u,c=!!a&&!!a.unsafe,s=!!a&&!!a.enumerable,f=!!a&&!!a.noTargetGet;"function"==typeof i&&("string"!=typeof e||S(i,"name")||j(i,"name",e),(u=r(i)).source||(u.source=n.join("string"==typeof e?e:""))),t!==o?(c?!f&&t[e]&&(s=!0):delete t[e],s?t[e]=i:j(t,e,i)):s?t[e]=i:P(e,i)})(Function.prototype,"toString",(function(){return"function"==typeof this&&e(this).source||D(this)}))})),et=o,rt=function(t){return"function"==typeof t?t:void 0},nt=function(t,e){return arguments.length<2?rt(et[t])||rt(o[t]):et[t]&&et[t][e]||o[t]&&o[t][e]},ot=Math.ceil,it=Math.floor,at=function(t){return isNaN(t=+t)?0:(t>0?it:ot)(t)},ut=Math.min,ct=function(t){return t>0?ut(at(t),9007199254740991):0},st=Math.max,ft=Math.min,lt=function(t,e){var r=at(t);return r<0?st(r+e,0):ft(r,e)},ht=function(t){return function(e,r,n){var o,i=g(e),a=ct(i.length),u=lt(n,a);if(t&&r!=r){for(;a>u;)if((o=i[u++])!=o)return!0}else for(;a>u;u++)if((t||u in i)&&i[u]===r)return t||u||0;return!t&&-1}},pt={includes:ht(!0),indexOf:ht(!1)},dt=pt.indexOf,vt=function(t,e){var r,n=g(t),o=0,i=[];for(r in n)!S($,r)&&S(n,r)&&i.push(r);for(;e.length>o;)S(n,r=e[o++])&&(~dt(i,r)||i.push(r));return i},gt=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"],yt=gt.concat("length","prototype"),mt={f:Object.getOwnPropertyNames||function(t){return vt(t,yt)}},bt={f:Object.getOwnPropertySymbols},St=nt("Reflect","ownKeys")||function(t){var e=mt.f(A(t)),r=bt.f;return r?e.concat(r(t)):e},Et=function(t,e){for(var r=St(e),n=_.f,o=x.f,i=0;i2?arguments[2]:void 0,u=Nt((void 0===a?n:lt(a,n))-i,n-o),c=1;for(i0;)i in r?r[o]=r[i]:delete r[o],o+=c,i+=c;return r},Ut=!!Object.getOwnPropertySymbols&&!i((function(){return!String(Symbol())})),kt=Ut&&!Symbol.sham&&"symbol"==typeof Symbol.iterator,Lt=B("wks"),Dt=o.Symbol,Ct=kt?Dt:Dt&&Dt.withoutSetter||G,Ft=function(t){return S(Lt,t)||(Lt[t]=Ut&&S(Dt,t)?Dt[t]:Ct("Symbol."+t)),Lt[t]},Bt=Object.keys||function(t){return vt(t,gt)},Wt=a?Object.defineProperties:function(t,e){A(t);for(var r,n=Bt(e),o=n.length,i=0;o>i;)_.f(t,r=n[i++],e[r]);return t},zt=nt("document","documentElement"),Gt=V("IE_PROTO"),Kt=function(){},Vt=function(t){return"
\ No newline at end of file diff --git a/static/6871f6b27b75646eb567bc25cd7d7d24/14b42/hello.jpg b/static/6871f6b27b75646eb567bc25cd7d7d24/14b42/hello.jpg new file mode 100644 index 0000000..c758a0c Binary files /dev/null and b/static/6871f6b27b75646eb567bc25cd7d7d24/14b42/hello.jpg differ diff --git a/static/6871f6b27b75646eb567bc25cd7d7d24/2244e/hello.jpg b/static/6871f6b27b75646eb567bc25cd7d7d24/2244e/hello.jpg new file mode 100644 index 0000000..dcd2154 Binary files /dev/null and b/static/6871f6b27b75646eb567bc25cd7d7d24/2244e/hello.jpg differ diff --git a/static/6871f6b27b75646eb567bc25cd7d7d24/47498/hello.jpg b/static/6871f6b27b75646eb567bc25cd7d7d24/47498/hello.jpg new file mode 100644 index 0000000..0334059 Binary files /dev/null and b/static/6871f6b27b75646eb567bc25cd7d7d24/47498/hello.jpg differ diff --git a/static/6871f6b27b75646eb567bc25cd7d7d24/f836f/hello.jpg b/static/6871f6b27b75646eb567bc25cd7d7d24/f836f/hello.jpg new file mode 100644 index 0000000..69b55c5 Binary files /dev/null and b/static/6871f6b27b75646eb567bc25cd7d7d24/f836f/hello.jpg differ diff --git a/static/Inter-UI-Bold-e2158f600a4b0175b73dfefb0d2fe8f8.woff b/static/Inter-UI-Bold-e2158f600a4b0175b73dfefb0d2fe8f8.woff new file mode 100644 index 0000000..8fefb2d Binary files /dev/null and b/static/Inter-UI-Bold-e2158f600a4b0175b73dfefb0d2fe8f8.woff differ diff --git a/static/Inter-UI-BoldItalic-dbe4ec1d26d41331c91550939a7fd27f.woff b/static/Inter-UI-BoldItalic-dbe4ec1d26d41331c91550939a7fd27f.woff new file mode 100644 index 0000000..8e0e289 Binary files /dev/null and b/static/Inter-UI-BoldItalic-dbe4ec1d26d41331c91550939a7fd27f.woff differ diff --git a/static/Inter-UI-Italic-bcf654c7bea165e9dae257206c5521ad.woff b/static/Inter-UI-Italic-bcf654c7bea165e9dae257206c5521ad.woff new file mode 100644 index 0000000..70c132b Binary files /dev/null and b/static/Inter-UI-Italic-bcf654c7bea165e9dae257206c5521ad.woff differ diff --git a/static/Inter-UI-Medium-48dfecfe37de0ef8f0e2c2d2e6992e9c.woff b/static/Inter-UI-Medium-48dfecfe37de0ef8f0e2c2d2e6992e9c.woff new file mode 100644 index 0000000..8aef504 Binary files /dev/null and b/static/Inter-UI-Medium-48dfecfe37de0ef8f0e2c2d2e6992e9c.woff differ diff --git a/static/Inter-UI-MediumItalic-a9eddc92c84c5f78fba1cb7d7833d31c.woff b/static/Inter-UI-MediumItalic-a9eddc92c84c5f78fba1cb7d7833d31c.woff new file mode 100644 index 0000000..58119ef Binary files /dev/null and b/static/Inter-UI-MediumItalic-a9eddc92c84c5f78fba1cb7d7833d31c.woff differ diff --git a/static/Inter-UI-Regular-ecf7c639683dfcb4868861e0c613c455.woff b/static/Inter-UI-Regular-ecf7c639683dfcb4868861e0c613c455.woff new file mode 100644 index 0000000..2ada3d0 Binary files /dev/null and b/static/Inter-UI-Regular-ecf7c639683dfcb4868861e0c613c455.woff differ diff --git a/static/Spoqa Han Sans Bold-4ad74ffe34b23dc2e4e4ab8d1e1871c5.woff b/static/Spoqa Han Sans Bold-4ad74ffe34b23dc2e4e4ab8d1e1871c5.woff new file mode 100644 index 0000000..e793d7b Binary files /dev/null and b/static/Spoqa Han Sans Bold-4ad74ffe34b23dc2e4e4ab8d1e1871c5.woff differ diff --git a/static/Spoqa Han Sans Light-9e83e4227b99ea4cfd8dcf052b6c9a8b.woff b/static/Spoqa Han Sans Light-9e83e4227b99ea4cfd8dcf052b6c9a8b.woff new file mode 100644 index 0000000..ee141f0 Binary files /dev/null and b/static/Spoqa Han Sans Light-9e83e4227b99ea4cfd8dcf052b6c9a8b.woff differ diff --git a/static/Spoqa Han Sans Regular-5ffec7731901dc0b720f9bf1e541abef.woff b/static/Spoqa Han Sans Regular-5ffec7731901dc0b720f9bf1e541abef.woff new file mode 100644 index 0000000..0389d83 Binary files /dev/null and b/static/Spoqa Han Sans Regular-5ffec7731901dc0b720f9bf1e541abef.woff differ diff --git a/static/Spoqa Han Sans Thin-a1a1697fa12d14eaee122f2aeed3215c.woff b/static/Spoqa Han Sans Thin-a1a1697fa12d14eaee122f2aeed3215c.woff new file mode 100644 index 0000000..4d8d33e Binary files /dev/null and b/static/Spoqa Han Sans Thin-a1a1697fa12d14eaee122f2aeed3215c.woff differ diff --git a/static/c7474cab18c535fb1df08669dc5d73bd/2244e/atdd.jpg b/static/c7474cab18c535fb1df08669dc5d73bd/2244e/atdd.jpg new file mode 100644 index 0000000..890ad2b Binary files /dev/null and b/static/c7474cab18c535fb1df08669dc5d73bd/2244e/atdd.jpg differ diff --git a/static/c7474cab18c535fb1df08669dc5d73bd/a6407/atdd.jpg b/static/c7474cab18c535fb1df08669dc5d73bd/a6407/atdd.jpg new file mode 100644 index 0000000..2eebc10 Binary files /dev/null and b/static/c7474cab18c535fb1df08669dc5d73bd/a6407/atdd.jpg differ diff --git a/static/c7474cab18c535fb1df08669dc5d73bd/a6b4f/atdd.jpg b/static/c7474cab18c535fb1df08669dc5d73bd/a6b4f/atdd.jpg new file mode 100644 index 0000000..614cd2f Binary files /dev/null and b/static/c7474cab18c535fb1df08669dc5d73bd/a6b4f/atdd.jpg differ diff --git a/static/c7474cab18c535fb1df08669dc5d73bd/b2a14/atdd.jpg b/static/c7474cab18c535fb1df08669dc5d73bd/b2a14/atdd.jpg new file mode 100644 index 0000000..79b77d0 Binary files /dev/null and b/static/c7474cab18c535fb1df08669dc5d73bd/b2a14/atdd.jpg differ diff --git a/static/c7474cab18c535fb1df08669dc5d73bd/c2c47/atdd.jpg b/static/c7474cab18c535fb1df08669dc5d73bd/c2c47/atdd.jpg new file mode 100644 index 0000000..cdffb3f Binary files /dev/null and b/static/c7474cab18c535fb1df08669dc5d73bd/c2c47/atdd.jpg differ diff --git a/static/c7474cab18c535fb1df08669dc5d73bd/f836f/atdd.jpg b/static/c7474cab18c535fb1df08669dc5d73bd/f836f/atdd.jpg new file mode 100644 index 0000000..17c21d8 Binary files /dev/null and b/static/c7474cab18c535fb1df08669dc5d73bd/f836f/atdd.jpg differ diff --git a/static/c81841b5cef75b0b55bcdc087166ab1f/65fd0/tdd.jpg b/static/c81841b5cef75b0b55bcdc087166ab1f/65fd0/tdd.jpg new file mode 100644 index 0000000..d313a67 Binary files /dev/null and b/static/c81841b5cef75b0b55bcdc087166ab1f/65fd0/tdd.jpg differ diff --git a/static/c81841b5cef75b0b55bcdc087166ab1f/a6407/tdd.jpg b/static/c81841b5cef75b0b55bcdc087166ab1f/a6407/tdd.jpg new file mode 100644 index 0000000..3477a59 Binary files /dev/null and b/static/c81841b5cef75b0b55bcdc087166ab1f/a6407/tdd.jpg differ diff --git a/static/c81841b5cef75b0b55bcdc087166ab1f/a6b4f/tdd.jpg b/static/c81841b5cef75b0b55bcdc087166ab1f/a6b4f/tdd.jpg new file mode 100644 index 0000000..de7cf26 Binary files /dev/null and b/static/c81841b5cef75b0b55bcdc087166ab1f/a6b4f/tdd.jpg differ diff --git a/static/ecd2544a4fc178e7a5580217cf26223e/2a4de/atdd_tdd.png b/static/ecd2544a4fc178e7a5580217cf26223e/2a4de/atdd_tdd.png new file mode 100644 index 0000000..abb63b1 Binary files /dev/null and b/static/ecd2544a4fc178e7a5580217cf26223e/2a4de/atdd_tdd.png differ diff --git a/static/ecd2544a4fc178e7a5580217cf26223e/497c6/atdd_tdd.png b/static/ecd2544a4fc178e7a5580217cf26223e/497c6/atdd_tdd.png new file mode 100644 index 0000000..d9f8692 Binary files /dev/null and b/static/ecd2544a4fc178e7a5580217cf26223e/497c6/atdd_tdd.png differ diff --git a/static/ecd2544a4fc178e7a5580217cf26223e/69585/atdd_tdd.png b/static/ecd2544a4fc178e7a5580217cf26223e/69585/atdd_tdd.png new file mode 100644 index 0000000..7849625 Binary files /dev/null and b/static/ecd2544a4fc178e7a5580217cf26223e/69585/atdd_tdd.png differ diff --git a/styles-407fe62976dc5310c43e.js b/styles-407fe62976dc5310c43e.js new file mode 100644 index 0000000..d1b1f3f --- /dev/null +++ b/styles-407fe62976dc5310c43e.js @@ -0,0 +1,2 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[0],[]]); +//# sourceMappingURL=styles-407fe62976dc5310c43e.js.map \ No newline at end of file diff --git a/styles-407fe62976dc5310c43e.js.map b/styles-407fe62976dc5310c43e.js.map new file mode 100644 index 0000000..4aba90c --- /dev/null +++ b/styles-407fe62976dc5310c43e.js.map @@ -0,0 +1 @@ +{"version":3,"sources":[],"names":[],"mappings":"","file":"styles-407fe62976dc5310c43e.js","sourceRoot":""} \ No newline at end of file diff --git a/styles.bde44906a46dc3cfde0c.css b/styles.bde44906a46dc3cfde0c.css new file mode 100644 index 0000000..4f6c937 --- /dev/null +++ b/styles.bde44906a46dc3cfde0c.css @@ -0,0 +1 @@ +.icon-module--root--aZeph{display:inline-flex;align-items:center;justify-content:center}.icon-module--icon--1ihz7{fill:currentColor}.icon-module--label--fHC-f{position:relative;display:inline-block;margin-left:4px;line-height:1}.menu-module--mobileMenuContainer--T9wTN{display:none}@media (max-width:683px){.menu-module--mobileMenuContainer--T9wTN{display:flex}}.menu-module--desktopMenuContainer--3iXKR{display:block}@media (max-width:683px){.menu-module--desktopMenuContainer--3iXKR{display:none}}.menu-module--menu--dd4-Y{display:flex;flex:0 0 auto;align-items:center;justify-content:flex-start;max-width:100%;padding:0 15px;list-style:none;border-right:1px solid;margin:0 18px 0 auto}.menu-module--menu--dd4-Y li{margin:0 12px}.menu-module--menuTrigger--32seM{margin-right:10px;padding:0;line-height:0;background:none;color:inherit;border:none;box-shadow:none;outline:none;-webkit-appearance:none;-moz-appearance:none;appearance:none;cursor:pointer}.menu-module--menu--dd4-Y a{display:inline-block;margin-right:15px;text-decoration:none}.menu-module--menu--dd4-Y a:last-of-type{margin-right:0}.menu-module--mobileMenu--2VGmR{position:absolute;top:0;right:0;flex-direction:column;align-items:flex-start;background:#fafafa;margin:0;padding:0;text-align:left;list-style:none;border-radius:5px;overflow:hidden;z-index:99}.dark-theme .menu-module--mobileMenu--2VGmR{background:#252627}.menu-module--mobileMenu--2VGmR li{margin:0;white-space:nowrap}.menu-module--mobileMenu--2VGmR li a{display:block;padding:10px 15px}.menu-module--mobileMenuOverlay--2RcSs{position:fixed;top:0;bottom:0;left:0;right:0;background:rgba(0,0,0,.2);z-index:10}.menu-module--themeToggle--2E8ye{line-height:0;padding:0 5px}.menu-module--subMenuTrigger--1crdm,.menu-module--themeToggle--2E8ye{background:none;color:inherit;border:none;box-shadow:none;-webkit-appearance:none;-moz-appearance:none;appearance:none;outline:none}.menu-module--subMenuTrigger--1crdm{font-size:inherit;font-weight:inherit;margin:0 12px;padding:0;cursor:pointer}.menu-module--subMenu--3Rgj_{position:absolute;max-width:300px;background:#fafafa;box-shadow:0 8px 20px rgba(0,0,0,.12);margin:0;padding:5px;list-style:none;border-radius:5px;top:35px;right:70px;overflow:hidden;z-index:99}.dark-theme .menu-module--subMenu--3Rgj_{background:#3b3d42}.menu-module--subMenu--3Rgj_ li{text-align:left;margin:0;white-space:nowrap}.menu-module--subMenu--3Rgj_ li a{padding:10px}.menu-module--subMenu--3Rgj_ li:hover{background:rgba(0,0,0,.05);border-radius:3px;cursor:pointer}.dark-theme .menu-module--subMenu--3Rgj_ li:hover{background:rgba(0,0,0,.15)}.menu-module--subMenuOverlay--3TQRy{position:fixed;top:0;bottom:0;left:0;right:0;z-index:-1}.menu-module--menuArrow--1COMf{display:inline-block;font-family:Inter UI;margin-left:5px;transform:rotate(90deg)}.header-module--header--tFeG9{background:#fafafa;display:flex;align-items:center;justify-content:space-between;position:relative;padding:20px}.dark-theme .header-module--header--tFeG9{background:#252627}.header-module--header--tFeG9 a{text-decoration:none}.header-module--inner--7ao1M{justify-content:space-between;margin:0 auto;width:760px;max-width:100%}.header-module--inner--7ao1M,.header-module--logo--2kX-3{display:flex;align-items:center}.header-module--logo--2kX-3{text-decoration:none;font-weight:700}.header-module--logo--2kX-3 img{height:44px}.header-module--mark--J6C0c{margin-right:5px}.header-module--cursor--33Aoa{display:inline-block;width:10px;height:1rem;background:#fe5186;margin-left:5px;border-radius:1px;-webkit-animation:header-module--cursor--33Aoa 1s infinite;animation:header-module--cursor--33Aoa 1s infinite}.header-module--mark--J6C0c,.header-module--text--3UAQj{font-size:18px}.header-module--right--1Zeb6{display:flex;position:relative}@-webkit-keyframes header-module--cursor--33Aoa{0%{opacity:0}50%{opacity:1}to{opacity:0}}@keyframes header-module--cursor--33Aoa{0%{opacity:0}50%{opacity:1}to{opacity:0}}@font-face{font-family:Inter UI;font-style:normal;font-weight:400;src:url(/static/Inter-UI-Regular-ecf7c639683dfcb4868861e0c613c455.woff) format("woff")}@font-face{font-family:Inter UI;font-style:italic;font-weight:400;src:url(/static/Inter-UI-Italic-bcf654c7bea165e9dae257206c5521ad.woff) format("woff")}@font-face{font-family:Inter UI;font-style:normal;font-weight:600;src:url(/static/Inter-UI-Medium-48dfecfe37de0ef8f0e2c2d2e6992e9c.woff) format("woff")}@font-face{font-family:Inter UI;font-style:italic;font-weight:600;src:url(/static/Inter-UI-MediumItalic-a9eddc92c84c5f78fba1cb7d7833d31c.woff) format("woff")}@font-face{font-family:Inter UI;font-style:normal;font-weight:800;src:url(/static/Inter-UI-Bold-e2158f600a4b0175b73dfefb0d2fe8f8.woff) format("woff")}@font-face{font-family:Inter UI;font-style:italic;font-weight:800;src:url(/static/Inter-UI-BoldItalic-dbe4ec1d26d41331c91550939a7fd27f.woff) format("woff")}@font-face{font-family:Spoqa Han Sans;font-weight:700;src:local("Spoqa Han Sans Bold"),url("/static/Spoqa Han Sans Bold-4ad74ffe34b23dc2e4e4ab8d1e1871c5.woff") format("woff")}@font-face{font-family:Spoqa Han Sans;font-weight:400;src:local("Spoqa Han Sans Regular"),url("/static/Spoqa Han Sans Regular-5ffec7731901dc0b720f9bf1e541abef.woff") format("woff")}@font-face{font-family:Spoqa Han Sans;font-weight:300;src:local("Spoqa Han Sans Light"),url("/static/Spoqa Han Sans Light-9e83e4227b99ea4cfd8dcf052b6c9a8b.woff") format("woff")}@font-face{font-family:Spoqa Han Sans;font-weight:100;src:local("Spoqa Han Sans Thin"),url("/static/Spoqa Han Sans Thin-a1a1697fa12d14eaee122f2aeed3215c.woff") format("woff")}pre[class*=language-],pre[class*=language-]>code{color:#a9a9b3;background:none;font-family:Consolas,Monaco,Andale Mono,Ubuntu Mono,monospace;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto;background:#1a1a1d!important;border-radius:8px}.token.block-comment,.token.cdata,.token.comment,.token.doctype,.token.prolog{color:hsla(0,0%,100%,.35)}.token.punctuation{color:#a9a9b3}.token.attr-name,.token.deleted,.token.namespace,.token.tag{color:#e2777a}.token.function-name{color:#6196cc}.token.boolean,.token.function,.token.number{color:#f08d49}.token.class-name,.token.constant,.token.property,.token.symbol{color:#f8c555}.token.atrule,.token.builtin,.token.important,.token.keyword,.token.selector{color:#cc99cd}.token.attr-value,.token.char,.token.regex,.token.string,.token.variable{color:#7ec699}.token.entity,.token.operator,.token.url{color:#67cdcc}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.inserted{color:green}pre[class*=language-].line-numbers{position:relative;padding-left:65px;counter-reset:linenumber}pre[class*=language-].line-numbers>code{position:relative;white-space:inherit}.line-numbers .line-numbers-rows{position:absolute;pointer-events:none;top:1em;font-size:100%;left:5px!important;width:3em!important;letter-spacing:-1px;border-right:1px solid;opacity:.5;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.line-numbers-rows>span{pointer-events:none;display:block;counter-increment:linenumber}.line-numbers-rows>span:before{content:counter(linenumber);color:#999;display:block;padding-right:.8em;text-align:right}div.code-toolbar{position:relative}div.code-toolbar>.toolbar{position:absolute;top:.3em;right:.2em;transition:opacity .3s ease-in-out;opacity:0}div.code-toolbar:hover>.toolbar{opacity:1}div.code-toolbar>.toolbar .toolbar-item{display:inline-block}div.code-toolbar>.toolbar a{cursor:pointer}div.code-toolbar>.toolbar button{background:none;border:0;color:inherit;font:inherit;line-height:normal;overflow:visible;padding:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}div.code-toolbar>.toolbar a,div.code-toolbar>.toolbar button,div.code-toolbar>.toolbar span{color:#bbb;font-size:.8em;padding:0 .5em;background:#f5f2f0;background:hsla(0,0%,87.8%,.2);box-shadow:0 2px 0 0 rgba(0,0,0,.2);border-radius:.5em}div.code-toolbar>.toolbar a:focus,div.code-toolbar>.toolbar a:hover,div.code-toolbar>.toolbar button:focus,div.code-toolbar>.toolbar button:hover,div.code-toolbar>.toolbar span:focus,div.code-toolbar>.toolbar span:hover{color:inherit;text-decoration:none}.command-line-prompt{border-right:1px solid #999;display:block;float:left;font-size:100%;letter-spacing:-1px;margin-right:1em;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.command-line-prompt>span:before{color:#999;content:" ";display:block;padding-right:.8em}.command-line-prompt>span[data-user]:before{content:"[" attr(data-user) "@" attr(data-host) "] $"}.command-line-prompt>span[data-user=root]:before{content:"[" attr(data-user) "@" attr(data-host) "] #"}.command-line-prompt>span[data-prompt]:before{content:attr(data-prompt)}html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}body{margin:0;padding:0;font-family:Inter UI,-apple-system,BlinkMacSystemFont,Roboto,Segoe UI,Helvetica,Arial,sans-serif;font-size:1rem;line-height:1.54;background-color:#fff;color:#222;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;font-feature-settings:"liga","tnum","case","calt","zero","ss01","locl";-webkit-overflow-scrolling:touch;-webkit-text-size-adjust:100%}@media (max-width:683px){body{font-size:1rem}}body.dark-theme{background-color:#292a2d;color:#a9a9b3}h1,h2,h3,h4,h5,h6{display:flex;align-items:center;line-height:1.3}h1{font-size:2.625rem}h2{font-size:1.625rem}h3{font-size:1.375rem}h4{font-size:1.125rem}@media (max-width:683px){h1{font-size:2rem}h2{font-size:1.4rem}h3{font-size:1.15rem}h4{font-size:1.125rem}}a{color:inherit}img{display:block;max-width:100%;margin:0 auto .5rem}img.center,img.left{margin-right:auto}img.center,img.right{margin-left:auto}figure{display:table;max-width:100%;margin:25px 0}figure.center,figure.left{margin-right:auto}figure.center,figure.right{margin-left:auto}figure figcaption{font-size:14px;margin-top:5px;opacity:.8}figure figcaption.left{text-align:left}figure figcaption.center{text-align:center}figure figcaption.right{text-align:right}code{font-family:Consolas,Monaco,Andale Mono,Ubuntu Mono,monospace;font-feature-settings:normal;font-weight:400;background:#eaeaea;padding:1px 6px;margin:0 2px;border-radius:5px;font-size:.9rem}.dark-theme code{background:#3b3d42}pre{background:#1a1a1d;padding:20px;border-radius:8px;font-size:.9rem;overflow:auto}@media (max-width:683px){pre{white-space:pre-wrap;word-wrap:break-word}}pre code{background:none!important;color:#ccc;margin:0;padding:0;font-size:.9rem}blockquote{border-left:2px solid;margin:40px;padding:10px 20px}@media (max-width:683px){blockquote{margin:10px;padding:10px}}blockquote p:first-of-type{margin-top:0}blockquote p:last-of-type{margin-bottom:0}table{table-layout:fixed;border-collapse:collapse;width:100%;margin:40px 0;border-radius:5px}table,td,th{border:1px solid #222;padding:10px}.dark-theme table,.dark-theme td,.dark-theme th{border-color:#a9a9b3}th{background:#eaeaea}.dark-theme th{background:#3b3d42}ol,ul{margin-left:40px;padding:0}@media (max-width:683px){ol,ul{margin-left:20px}}ol ol{list-style-type:lower-alpha}.container{flex-direction:column;text-align:center}.container,.content{display:flex;justify-content:center}.content{flex-direction:column;flex:1 0 auto;align-items:center;margin:50px auto;width:100%;max-width:800px}@media (max-width:683px){.content{margin-top:0}}@media (max-width:899px){.content{max-width:660px}}hr{width:100%;border:none;background:#dcdcdc;height:1px}.dark-theme hr{background:#4a4b50}.infoBanner{text-align:left;margin:20px 0 40px;padding:10px 20px;border-radius:10px;width:calc(100% - 40px);background:#eaeaea}.dark-theme .infoBanner{background:#3b3d42}.infoBanner span{font-weight:700}.hidden{display:none}.embedVideo-container{position:relative;padding-bottom:56.25%;height:0;overflow:hidden}.embedVideo-container iframe{position:absolute;top:0;left:0;width:100%;height:100%;border:0}footer{font-size:1rem;text-align:center;margin-bottom:50px}@media (max-width:683px){footer{display:flex;flex-direction:column}}footer .footerCopyrights:not(:first-of-type){margin-left:20px;padding-left:20px;border-left:1px solid}@media (max-width:683px){footer .footerCopyrights:not(:first-of-type){margin:0;padding:0;border:none}}.gatsby-resp-image-wrapper{margin-top:2rem;margin-bottom:2rem}.navigation-module--navigation--3Zfju{display:flex;width:1024px;max-width:100%;margin:80px 0 40px}.navigation-module--button--28kp3,.navigation-module--navigation--3Zfju{align-items:center;justify-content:center}.navigation-module--button--28kp3{position:relative;display:inline-flex;background:#eaeaea;font-size:1rem;font-weight:700;border-radius:8px;max-width:40%;cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none}.dark-theme .navigation-module--button--28kp3{background:#3b3d42}.navigation-module--button--28kp3+.navigation-module--button--28kp3{margin-left:10px}.navigation-module--button--28kp3 a{display:flex;padding:8px 16px;text-decoration:none}.navigation-module--button--28kp3 a,.navigation-module--buttonText--1Xod2{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.navigation-module--iconNext--3xyJ-{margin-left:8px}.navigation-module--iconPrev--23mg1{margin-right:8px}.post-module--post--28Mq2{width:100%;max-width:800px;text-align:left;padding:20px;margin:0 auto 20px}.post-module--post--28Mq2:not(:last-of-type){border-bottom:1px solid #dcdcdc}.dark-theme .post-module--post--28Mq2:not(:last-of-type){border-color:#4a4b50}@media (max-width:899px){.post-module--post--28Mq2{max-width:660px}}.post-module--post--28Mq2 h1{margin:0 0 10px}.post-module--post--28Mq2 img{border-radius:8px}.post-module--title--3XBo2 a{text-decoration:none}.post-module--coverImage--1GM7V{border-radius:8px;margin-bottom:40px;box-shadow:0 15px 30px rgba(0,0,0,.1)}.post-module--meta--3YtjE{font-size:1rem;margin-bottom:30px}.post-module--tags--3RbqF{margin-top:10px;opacity:.5}.post-module--tag--16U9p{display:inline-block;margin-right:10px}.post-module--series--3YUjN{margin-top:2rem;margin-bottom:2rem;padding:2rem 1.5rem;border-radius:8px;box-shadow:0 0 4px 0 rgba(0,0,0,.06);position:relative;background:#eaeaea}.dark-theme .post-module--series--3YUjN{background:#3b3d42}.post-module--series-item--mOT0Y{display:block}.post-module--series-item-bold--2Vyvw{display:block;color:#fe5186}.post-module--series--3YUjN a{text-decoration:none}.post-module--series--3YUjN a:hover{text-decoration:underline}.post-module--readMore--3zWML,.post-module--series--3YUjN span{text-decoration:none}.post-module--readMore--3zWML{display:inline-block;font-weight:700;margin:20px 0;font-size:1rem}.post-module--postContent--1bfnt{position:relative}.post-module--postContent--1bfnt h2,.post-module--postContent--1bfnt h3{padding-top:2rem} \ No newline at end of file diff --git a/tag/ATDD/index.html b/tag/ATDD/index.html new file mode 100644 index 0000000..b53d807 --- /dev/null +++ b/tag/ATDD/index.html @@ -0,0 +1,5 @@ +맨땅에 코딩
Posts with tag: #ATDD

가볍게 시작하는 인수 테스트 주도 개발

20 November 2020 — Written by Boorownie
#ATDD#TDD#BDD

이번 시리즈에서는 인수 테스트 주도 개발을 으로 접근하여 용어 정리를 하고 예시를 통해 적용하는 방법을 알아보겠습니다. TDD 많은 분들이 아시다시피 TDD 사이클은…

ATDD with Spring Boot

30 November 2019 — Written by Boorownie
#ATDD#Spring#Spring Boot

TDD를 배우면서 도메인에 먼저 집중하여 도메인 기능의 테스트 코드를 구현한 후 기능을 구현하는 방법으로 학습합니다. +한 번에 한 가지를 집중하고 각 도메인들은 애플리케이션이 구성되기 전에 작성됩니다. +하지만 ATDD…

ATDD 프로세스

29 November 2019 — Written by Boorownie
#ATDD#Agile

ATDD(Acceptance Test Driven Development…

\ No newline at end of file diff --git a/tag/Agile/index.html b/tag/Agile/index.html new file mode 100644 index 0000000..073a2f6 --- /dev/null +++ b/tag/Agile/index.html @@ -0,0 +1,3 @@ +맨땅에 코딩
Posts with tag: #Agile

ATDD 프로세스

29 November 2019 — Written by Boorownie
#ATDD#Agile

ATDD(Acceptance Test Driven Development…

\ No newline at end of file diff --git a/tag/BDD/index.html b/tag/BDD/index.html new file mode 100644 index 0000000..fcc9ae8 --- /dev/null +++ b/tag/BDD/index.html @@ -0,0 +1,3 @@ +맨땅에 코딩
Posts with tag: #BDD

가볍게 시작하는 인수 테스트 주도 개발

20 November 2020 — Written by Boorownie
#ATDD#TDD#BDD

이번 시리즈에서는 인수 테스트 주도 개발을 으로 접근하여 용어 정리를 하고 예시를 통해 적용하는 방법을 알아보겠습니다. TDD 많은 분들이 아시다시피 TDD 사이클은…

\ No newline at end of file diff --git a/tag/Spring-Boot/index.html b/tag/Spring-Boot/index.html new file mode 100644 index 0000000..43476a5 --- /dev/null +++ b/tag/Spring-Boot/index.html @@ -0,0 +1,5 @@ +맨땅에 코딩
Posts with tag: #Spring Boot

ATDD with Spring Boot

30 November 2019 — Written by Boorownie
#ATDD#Spring#Spring Boot

TDD를 배우면서 도메인에 먼저 집중하여 도메인 기능의 테스트 코드를 구현한 후 기능을 구현하는 방법으로 학습합니다. +한 번에 한 가지를 집중하고 각 도메인들은 애플리케이션이 구성되기 전에 작성됩니다. +하지만 ATDD…

\ No newline at end of file diff --git a/tag/Spring/index.html b/tag/Spring/index.html new file mode 100644 index 0000000..1aa0b00 --- /dev/null +++ b/tag/Spring/index.html @@ -0,0 +1,5 @@ +맨땅에 코딩
Posts with tag: #Spring

ATDD with Spring Boot

30 November 2019 — Written by Boorownie
#ATDD#Spring#Spring Boot

TDD를 배우면서 도메인에 먼저 집중하여 도메인 기능의 테스트 코드를 구현한 후 기능을 구현하는 방법으로 학습합니다. +한 번에 한 가지를 집중하고 각 도메인들은 애플리케이션이 구성되기 전에 작성됩니다. +하지만 ATDD…

\ No newline at end of file diff --git a/tag/TDD/index.html b/tag/TDD/index.html new file mode 100644 index 0000000..441083c --- /dev/null +++ b/tag/TDD/index.html @@ -0,0 +1,3 @@ +맨땅에 코딩
Posts with tag: #TDD

가볍게 시작하는 인수 테스트 주도 개발

20 November 2020 — Written by Boorownie
#ATDD#TDD#BDD

이번 시리즈에서는 인수 테스트 주도 개발을 으로 접근하여 용어 정리를 하고 예시를 통해 적용하는 방법을 알아보겠습니다. TDD 많은 분들이 아시다시피 TDD 사이클은…

\ No newline at end of file diff --git a/webpack-runtime-0e7f98a7696909a3e816.js b/webpack-runtime-0e7f98a7696909a3e816.js new file mode 100644 index 0000000..7f60195 --- /dev/null +++ b/webpack-runtime-0e7f98a7696909a3e816.js @@ -0,0 +1,2 @@ +!function(e){function t(t){for(var n,o,s=t[0],d=t[1],u=t[2],i=0,f=[];i