Swift: Enumeracje

Opublikowano: 28.12.2023 - tagi: Swift Typ Enumeracja

Enumeracja

Enumeracja to zbiór wartości zgrupowanych w typie zdefiniowanym przez użytkownika. W Swift enumerację tworzy się za pomocą słowa kluczowego: enum:

enum TimerState {
	case start, pause, stop
}

Przy pomocy case określasz jakie wartości będzie mieć Twoja enumeracja.

Przykład:

enum TimerState {
	case start, pause, stop
}

var timerState: TimerState = .start

print("timer state: \(timerState)") // start

Zauważ, że opcja start ma wartość... start. Jest to domyślne zachowanie.

Możesz jednak nadać opcji inną wartość niż ta domyślna. To tzw.: surowe wartości (ang.: raw values)

Surowe wartości

enum ClothesSize: String {
	case S = "Small"
	case M = "Medium"
	case L = "Large"
}

var size: ClothesSize = .M

print(size) // M
print(size.rawValue) // Medium

Dla enumeracji ClothesSize zostały zdefiniowane trzy opcje. Do każdej opcji przypisane są wartości: Small, Medium, Large. To są właśnie wartości surowe.

Żeby się do nich dostać, użyj właściwości rawValue.

Opcjom enumeracji możesz nadać typ:

enum ClothesSize: Int {
	case S, M, L
}

Jaką surową wartość będzie mieć opcja S? Będzie to 0, a kolejne 1 i 2.

Możesz też napisać tak:

enum ClothesSize: Int {
	case S = 1, M, L
}

print(ClothesSize.S.rawValue) // 1

Swift "domyśli" się, że kolejne wartości będą to 2 i 3.

Iteracja po enum

Możesz także iterować po wszystkich opcjach enumeracji. Żeby to zrobić, musisz rozszerzyć enumerację za pomocą protokołu: CaseIterable:

enum TimerState: CaseIterable {
	case start, pause, stop
}

for current in TimerState.allCases {
  print(current)
}
// start
// pause
// stop

Żeby iterować po enumeracji, użyj właściwości allCases.

Możesz także iterować po surowych wartościach:

enum ClothesSize: String, CaseIterable {
	case S = "Small"
	case M = "Medium"
	case L = "Large"
}

for current in ClothesSize.allCases {
  print(current.rawValue)
}
// Small
// Medium
// Large

Switch i enum

Do obsługi opcji możesz użyć instrukcji switch:

enum TimerState {
	case start, pause, stop
}

var timerState: TimerState = .pause

switch timerState {
	case .start:
		print("Start timer")
	
	case .pause:
		print("Pause timer")

	case .stop:
		print("Stop timer")
}

// Pause timer

Właściwości w enum

W enumeracji możesz użyć computed property:

enum TimerState {
	case start, pause, stop
	
	var label: String {
		switch self {
			case .start:
				return "Timer s running"
				
			case .pause:
				return "Timer is paused"
				
			case .stop:
				return "Timer is stopped"
		}
	}
}

var timerState: TimerState = .start

print(timerState.label) // Timer is running

Za pomocą słowa kluczowego self pobierasz wartość, która przechowuje zmienna przy wywołaniu label.

Metody w enum

Swift umożliwia także używanie metod w enum:

enum TimerState {
	case start, pause, stop
	
	func getLabel() -> String {
		switch self {
			case .start:
				return "Timer s running"
				
			case .pause:
				return "Timer is paused"
				
			case .stop:
				return "Timer is stopped"
		}
	}
}

var timerState: TimerState = .stop

print(timerState.getLabel()) // Timer is stopped

Powiązane wartości

Enumeracja pozwala dołączyć do każdej opcji dodatkowe informacje. Nazywa się to: powiązane wartości (ang.: associated values).

Składnia wygląda następująco:

case someOption(Int)

Po podaniu nazwy opcji w nawiasach określasz dodatkową informację. Możesz dodać wiele opcji:

case someOption(Int, Int, String)

Dla jednego parametru możesz określić jakiś typ danych, a dla następnego użyć innego typu. Dodatkowo opcje mogą mieć różną liczbę parametrów:

case someOption(Int)
case otherOption(String, Double)

Przykład:

enum TimerState {
	case start(String)
	case pause(String)
	case stop(String)
}

var timerState: TimerState = .start("Timer is starting")

switch timerState {
	case .start(let label): 
		print("Label for start: \(label)")
		
	case .pause(let label): 
		print("Label for pause: \(label)")
		
	case .stop(let label): 
		print("Label for stop: \(label)")	
}

Możesz także nadać etykiety powiązanym wartościom:

case someOption(value: Int)

Kolejny przykład:

enum ClothesSize {
	case S(height: Int)
	case M(height: Int)
	case L(height: Int)
}

var size: ClothesSize = .S(height: 165)

switch size {
 case .S(let height):
   print("Height for S:", height)
   
 case .M(let height):
   print("Height for M:", height)
   
 case .L(let height):
   print("Height for L:", height)  
}

Nie możesz używać powiązanych wartości wraz z surowymi wartościami, w tym samym czasie.


React Testing Library: Klawiatura

Opublikowano: 14.12.2023 - tagi: JavaScript React Testowanie Test Klawiatura

Testowanie klawiatury

Do symulacji obsługi klawiatury posłuży nam niezawodna biblioteka user-event.

Załóżmy, że mam zwykłe pole tekstowe. Po wpisaniu danych i wciśnięciu przycisku Enter wartość powinna zostać dodana do listy.

Przykład:

import { useState } from "react";

export function MyComponent() {
    const [tasks, setTasks] = useState([]);
    const [task, setTask] = useState('');

    const onChangeTask = (e) => {
        setTask(e.target.value)
    };

    const onAddTask = (e) => {
        if (e.code == 'Enter' && task) {
            setTasks([task, ...tasks]);
            setTask('');
        }
    }

    return (
        <>
            <div>
                <label htmlFor="newTask">Task title:</label>
                <input
                    type="text"
                    id="newTask"
                    value={task}
                    onChange={onChangeTask}
                    onKeyDown={onAddTask}
                />
            </div>
            <ul>
                {
                    tasks.map((task, index) => (
                        <li key={index}>
                            {task}
                        </li>
                    ))
                }
            </ul>
        </>
    )
}

Test:

import {render, screen, within} from '@testing-library/react';
const { getByLabelText, getByTestId } = screen;

const renderComponent = () => {
    render(<MyComponent />);

    const addTaskField = getByLabelText('Task title:');
    const taskList = within(getByTestId('task-list'));
    const addTask = async (newTask) => {
        await userEvent.clear(addTaskField)
        await userEvent.type(addTaskField, newTask);
        await userEvent.keyboard('[Enter]');
    }

    return {
        addTaskField,
        taskList,
        addTask
    }
}

test('should add new task to the list', async () => {
    // given
    const { taskList, addTask } = renderComponent();

    // when
    await addTask('My task');

    // then
    expect(taskList.getByText('My task'));
});

Za dodanie zadania do listy odpowiedzialna jest w tym teście funkcja addTask. Wywołana jest tam funkcja keyboard z biblioteki user event, która w tym przypadku symuluje wciśnięcie przycisku Enter na klawiaturze.


Podsumowanie: Listopad 2023

Opublikowano: 30.11.2023 - tagi: Blog Podsumowanie Listopad 2023

We listopadzie opublikowałem 6 wpisów:


Swfit

  1. Klasy
  2. Protokoły
  3. Kontrola dostępu
  4. Extension

Narysowałem dwa komiksy:

  1. Coding rules
  2. Face the problem

Przeczytałem dwie książki:

  1. Outpost 2 — Dmitry Glukhovsky
  2. Dawno temu w Warszawie — Jakub Żulczyk

Przesłuchałem pięć audiobooki:

  1. Rozjemca — Brandon Sanderson
  2. Powróceni — Abdulrazak Gurnah
  3. Zło ze wschodu — Andrzej Pilipiuk
  4. Legion — Elżbieta Cherezińska
  5. Ziemiomorze. Czarnoksiężnik z Archipelagu — Ursula K. Le Guin

Comics: Face the problem

Opublikowano: 23.11.2023 - tagi: Komiks Rysowanie

Face the problem

Swift: Extension

Opublikowano: 21.11.2023 - tagi: Swift Struktura Klasa Protokół Typ Rozszerzenie

Rozszerzenia

Rozszerzenia w Swift służą do dodania funkcjonalności do już istniejącego typu. Może to być typ zarówno wbudowany w język, jak i stworzony przez programistę.

Rozszerzenie można dodać do klasy, struktury, protokołu, enumeracji i innych typów w Swift.

Żeby dodać nowe rozszerzenie, należy użyć słowa kluczowego extension.

Za pomocą rozszerzenie możesz dodać nową funkcjonalność, ale nie możesz nadpisać już istniejącej.

Przykłady

Typ własny

class Calculator {
	func add(a: Int, b: Int) -> Int {
		return a + b
	}
}

extension Calculator {
	func subtract(a: Int, b: Int) -> Int {
		return a - b
	}
}

var calculator = Calculator()

print(calculator.add(a: 7, b: 3)) // 10
print(calculator.subtract(a: 7, b: 3)) // 4

Typ wbudowany

Możesz też dodać rozszerzenie do typu wbudowanego:

extension Int {
	func square() -> Int {
	    return self * self
	}
}

var a: Int = 7
print("a * a =  \(a.square())") // 49
print("a = \(a)") // 7

Jeśli chcesz zmienić stan użyj słowa kluczowego mutating i nową wartość przypisz do self:

extension Int {
	mutating func square() {
	    self = self * self
	}
}

var a: Int = 7
a.square()
print("a = \(a)") // 49
a.square()
print("a = \(a)") // 2401

Computed property

Za pomocą rozszerzenia nie możesz dodać stored property:

extension SomeType {
	var property1: Int // Błąd!
}

ale możesz dodać computed property:

struct User {
	var firstName: String = ""
	var lastName: String = ""
}

extension User {
	var fullName: String {
		return firstName + " " + lastName
	}
}

var user = User(firstName: "James", lastName: "Bond");
print(user.fullName) // James Bond