Unique index or primary key violation

Unique index or primary key violation
MJ
MJ
  • Rejestracja:prawie 6 lat
  • Ostatnio:ponad 5 lat
  • Postów:8
0

Cześć,

Zacznę od tego, że szukałem na różnych forach rozwiązania tego problemu, niestety wszystkie proponowane rozwiązania u mnie nie działały. Piszę projekt imitujący zachowanie bloga, chciałem zacząć pisać testy integracyjne z wykorzystaniem JUnit 5 i bazy H2 inmemory.

Problem jest taki:

Kopiuj
Failed to execute SQL script statement #1 of URL [file:/E:/IntelliJ%20IDEA/projects/blog_mj/out/test/resources/data.sql]: insert into user (created_date, email, last_updated_date, password, username) values ( '2018-12-28 00:51:33', 'lizak0@live.com', '2018-05-03 13:31:25', 'nkfmVFGsXF', 'munwins0'); nested exception is org.h2.jdbc.JdbcSQLException: Unique index or primary key violation: "UK_OB8KQYQQGMEFL0ACO34AKDTPE_INDEX_2 ON PUBLIC.USER(EMAIL) VALUES ('lizak0@live.com', 1)"; SQL statement:
insert into user (created_date, email, last_updated_date, password, username) values ( '2018-12-28 00:51:33', 'lizak0@live.com', '2018-05-03 13:31:25', 'nkfmVFGsXF', 'munwins0')

Dzieje się to przy próbie odpalenia testu integracyjnego, wtedy, kiedy aplikacja próbuje załadować kontekst. Plik .sql, z którego robię inserty:

Kopiuj
insert into user (created_date, email, last_updated_date, password, username)
values ( '2018-12-28 00:51:33', 'lizak0@live.com', '2018-05-03 13:31:25', 'nkfmVFGsXF', 'munwins0');
insert into user (created_date, email, last_updated_date, password, username)
values ('2019-03-10 14:14:49', 'dchallin1@disqus.com', '2018-08-18 23:35:15', 'U6qZxNg7MHLY', 'fknibb1');
insert into user ( created_date, email, last_updated_date, password, username)
values ( '2018-08-26 22:38:12', 'enattriss2@wired.com', '2018-06-25 06:20:44', 'x4ZgJPYdr0', 'vchippindale2');
insert into user ( created_date, email, last_updated_date, password, username)
values ( '2018-08-02 07:39:46', 'tbarrett3@ca.gov', '2018-09-06 13:34:16', 'PNL635jpw', 'ccanniffe3');
insert into user ( created_date, email, last_updated_date, password, username)
values ( '2018-05-31 22:28:19', 'wverity4@home.pl', '2018-04-20 05:51:20', 'khL3XK', 'epochin4');

Z tych komunikatów, to jeśli dobrze rozumiem, zaburzyłem unikalność na kolumnie email, tylko że w insertach nigdzie nie mam duplikatów. Wie ktoś jak rozwiązać problem? Byłbym bardzo wdzięczny :D

JW
  • Rejestracja:prawie 6 lat
  • Ostatnio:ponad 5 lat
  • Lokalizacja:Wroclaw
  • Postów:12
0

Kiedy odpalasz tę SQLkę? Być może wykonuje się ona po prostu wielokrotnie (np. za każdym razem przed każdym kolejnym testem). Tak to wygląda bo sypie się już na pierszym insercie:)


edytowany 1x, ostatnio: jwrabel
MJ
MJ
  • Rejestracja:prawie 6 lat
  • Ostatnio:ponad 5 lat
  • Postów:8
0

Ta sqlka jest z pliku data.sql, który wykonuje się przy starcie aplikacji, jeśli się nie mylę?

Edit: to samo na MySQL wykonuje się bezproblemowo

edytowany 1x, ostatnio: Maciej Jeleń
JW
  • Rejestracja:prawie 6 lat
  • Ostatnio:ponad 5 lat
  • Lokalizacja:Wroclaw
  • Postów:12
0

Być może nie jest tworzona nowa za każdym razem. Spróbuj dać na początku drop table if exists user;


MJ
MJ
  • Rejestracja:prawie 6 lat
  • Ostatnio:ponad 5 lat
  • Postów:8
0

Dodałem tą linijkę przed każdą serią insertów iii:

Kopiuj
Failed to execute SQL script statement #2 of URL [file:/E:/IntelliJ%20IDEA/projects/blog_mj/out/test/resources/data.sql]: insert into user (created_date, email, last_updated_date, password, username) values ( '2018-12-28 00:51:33', 'lizak0@live.com', '2018-05-03 13:31:25', 'nkfmVFGsXF', 'munwins0'); nested exception is org.h2.jdbc.JdbcSQLException: Table "USER" not found; SQL statement:
insert into user (created_date, email, last_updated_date, password, username) values ( '2018-12-28 00:51:33', 'lizak0@live.com', '2018-05-03 13:31:25', 'nkfmVFGsXF', 'munwins0')

Czyli tak jakby Hibernate nie tworzył tej bazy danych przy starcie?

edytowany 1x, ostatnio: Maciej Jeleń
JW
  • Rejestracja:prawie 6 lat
  • Ostatnio:ponad 5 lat
  • Lokalizacja:Wroclaw
  • Postów:12
1

To usuwa Ci całą tabelę, po tym musisz ją stworzyć (create table ...) lub zamiast usuwania tabeli usunąć wszystkie dane - delete from user;

Edit: Nie zauważyłem, że używasz Hibernate'a. W takim razie użyj właśnie delete from user; przed dodawaniem:)


edytowany 2x, ostatnio: jwrabel
superdurszlak
  • Rejestracja:ponad 6 lat
  • Ostatnio:minuta
  • Lokalizacja:Kraków
  • Postów:1999
2

Tak na boku - weź zastąp trzymanie haseł w plaintext jakimś haszem z saltem - choćby BCrypt czy PBKDF2, zanim to komukolwiek pokażesz :P Zaraz będą niewygodne pytania dlaczego tak i czy jesteś pewien, że to bezpieczne i dlaczego nie haszujesz choć wiesz, że powinieneś.

To są dane wrzucane przy starcie aplikacji i co się później dzieje? Testy mają jakieś swoje dodatkowe migracje, dodające dane testowe, czy po prostu odpalasz test jakimś runnerem i tyle? Masz jakieś dodatkowe migracje specyficzne dla testów? Jeśli tak, czym je zaciągasz?

Spróbuj dać na początku drop table if exists user;

Miałeś chyba na myśli truncate table user? :P W sumie to dobre pytanie, czy H2 w ogóle wspiera truncate i jeśli tak, to czy ma w ogóle składnię taką samą, jak MySQL.

Ewentualnie możesz spróbować z insert ignore.


superdurszlak
rozleniwiłem się :]
MJ
MJ
  • Rejestracja:prawie 6 lat
  • Ostatnio:ponad 5 lat
  • Postów:8
0

W module main już te hasła są zahashowane BCryptem, tutaj tylko chcę, żeby zaczęło działać na bazie H2, później zajmę się poprawkami :)

Testy nie mają żadnych dodatkowych migracji, dodają to samo, co main przy starcie serwera.
Może to pomoże w rozwiązaniu problemu, klasa testowa wygląda tak:

Kopiuj
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class PostControllerITest {

    private MockMvc mockMvc;

    @Autowired
    private WebApplicationContext wac;

    @BeforeEach
    void setUp() {
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).apply(springSecurity()).build();
    }

    @Test
    @WithMockUser
    void findAllTest() throws Exception {
        mockMvc.perform(get("/posts"))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(jsonPath("$.posts.length()").value(10))
                .andExpect(jsonPath("$.posts.comments").doesNotExist())
                .andExpect(jsonPath("$.posts[0].tags.length()").value(3))
                .andExpect(jsonPath("$.posts[0].user").exists());
    }
}

@jwrabel delete from user; dla bazy H2 nie działa, wywala błąd: Referential constraint integrity violation (czy jakoś tak :D)

edytowany 1x, ostatnio: Maciej Jeleń
superdurszlak
  • Rejestracja:ponad 6 lat
  • Ostatnio:minuta
  • Lokalizacja:Kraków
  • Postów:1999
1

Ewentualnie możesz jeszcze pokombinować z @DirtiesContext, może gdzieś boczkiem przesiąkają jakieś babole od testu do testu? Czy to jest jedyny test póki co?

Wujek Baeldung też może pomoże :P


edytowany 1x, ostatnio: superdurszlak
MJ
MJ
  • Rejestracja:prawie 6 lat
  • Ostatnio:ponad 5 lat
  • Postów:8
0

To jest póki co jedyny test jaki mam (oprócz contextLoads() ). Dodałem też adnotację java @DirtiesContext ale nic nie pomogło.
Patrząc po logach, to ten plik data.sql tak jakby wykonywał się dwa razy, może tu jest problem?

superdurszlak
Patrząc po logach, to ten plik data.sql tak jakby wykonywał się dwa razy a weź no wrzuć tego loga
MJ
Maciej Jeleń
Trochę długi jest, mam wszystko wrzucić? :D
superdurszlak
możesz na forumowy pastebin jak nie chcesz spamować, albo wytnij jakiś większy fragment niż to co w pierwszym poscie :P
MJ
MJ
  • Rejestracja:prawie 6 lat
  • Ostatnio:ponad 5 lat
  • Postów:8
0
edytowany 1x, ostatnio: Maciej Jeleń
superdurszlak
  • Rejestracja:ponad 6 lat
  • Ostatnio:minuta
  • Lokalizacja:Kraków
  • Postów:1999
1

Pełny log: [...]

To akurat nie wygląda, jakby dwa razy wykonał się skrypt, po prostu wyjątek z JDBC jest owrapowany - chyba, że gdzieś wcześniej masz jakiś ślad po tym, że jednak gdzieś już migracja się wykonała. Nie logujesz tego, kiedy i które migracje są odpalane? Może faktycznie w tym kierunku trzeba podrążyć.

Prawdę mówiąc nie mając pełnego obrazu tego, jak masz wszystko skonfigurowane etc. nie jestem za bardzo w stanie Ci podpowiedzieć, co właściwie jest na rzeczy, ale może ktoś kto spędził więcej czasu na placu boju coś wymyśli. Nigdy nie próbowałem ręcznie bawić się z migracjami, w pracy używamy https://github.com/flyway/flyway-test-extensions i @FlywayTest do oznaczania testów integracyjnych itp, migracjami zajmuje się Flyway i dotąd nie wpędziliśmy się w podobny problem.


nie100sowny
  • Rejestracja:prawie 9 lat
  • Ostatnio:dzień
  • Lokalizacja:Kraków
  • Postów:402
2

Pokaż jdbc.url jaki używasz do tej bazy. Być może baza H2 utworzyła Ci bazę w pliku i nie skasowała.

Jedno rozwiązanie to by było tak jak wcześniej ktoś pisał:
drop table user if exists dać przed komendą create table user lub truncate table user dać po komendzie create table user. Zrobiłeś tak?

Drugie to zmienić jdbc.url aby H2 uruchamiało się in memory i razem z procesem JVM niszczyło.

Połączenie obu będzie najlepsze o ile nie robisz czegoś czego H2 mem nie wspiera.


"Gdy się nie wie, co się robi, to się dzieją takie rzeczy, że się nie wie, co się dzieje"
edytowany 4x, ostatnio: nie100sowny
KK
  • Rejestracja:ponad 16 lat
  • Ostatnio:18 dni
0

A spróbuj jeszcze ustawić spring.jpa.hibernate.ddl-auto=create-drop w application.properties czy lepiej w propertiesach odpowiednich dla Twojego profilu testowego (tam gdzie masz dane do h2 wpisane). Warto od razu zrobić sobie dwa profile, żebyś później przypadkiem produkcyjnej bazy nie wywalił.


YA
  • Rejestracja:prawie 10 lat
  • Ostatnio:około 3 godziny
  • Postów:2363
1

Nie masz tak, że testy wykonywane są na wielu wątkach i dlatego ładujesz do H2 te same dane równolegle?

MJ
MJ
  • Rejestracja:prawie 6 lat
  • Ostatnio:ponad 5 lat
  • Postów:8
0

@nie100sowny:
Mój jdbc url to: jdbc:h2:mem:test , a co do tych skryptów, to ja używam Hibernate, nie da się jakoś tego ominąć, żeby nie pisać tych DDLów? Wtedy Hibernate utworzyłby mi wszystkie tabele, usunął je i ponownie utworzył, ma to sens?

@kkojot:
Dokładnie tak mam, mam oddzielny profil (application.properties i data.sql) dla testów

@yarel:
Nie wiem, a jak to sprawdzić? :D

YA
@Maciej Jeleń: napisz jak te testy uruchamiasz.
superdurszlak
  • Rejestracja:ponad 6 lat
  • Ostatnio:minuta
  • Lokalizacja:Kraków
  • Postów:1999
1

Może zaczekajmy aż OP poda więcej szczegółów, póki co wiemy tylko tyle, że

  • używa H2 in-memory lub przynajmniej miał taki zamiar, ale mógł źle skonfigurować - tego nie wiemy, bo nie wiemy co siedzi w URL do bazy
  • póki co ma tylko ten jeden test i nic więcej
  • raczej nie korzysta z jakichś dedykowanych narzędzi do zarządzania i wersjonowania migracji, skoro ten skrypt SQL odpala się po prostu zawsze co start aplikacji. Migracja jest wrzucona do jakiegoś skryptu data.sql odpalanego defaultowo przez Spring Boot.

W ramach małęgo podsumowania, skoro wychodzi na to, że gdy migracja w teście rusza, to dane już tam są, to opcje są pewnie dwie trzy:

  • z jakiegoś powodu w teście skrypt odpala się dwukrotnie - np. dwukrotnie ładowany jest kontekst - zerknij tu i tu. OP - upewnij się, że ApplicationContext nie jest ładowany dwukrotnie w testach, że nie masz jakichś ustawień / configów mogących spowodować dwukrotne jego ładowanie itp.
  • baza faktycznie wcale nie jest in-memory i po testach / uruchomieniu aplikacji w bazie zostają śmieci które później kładą inicjalizację testu. OP - upewnij się, że baza H2 na 100% jest in-memory i że URL do bazy wskazuje faktycznie na coś w rodzaju jdbc:h2:mem:baza_testowa;OPCJA_1=ILEŚ;ETC
  • EDIT: zduplikowane dane w skryptach

Żeby to zfiksować, pewnie czeka Cię min. 1 z poniższych:

  • poprawić konfigurację i/lub wprowadzić poprawki - jeśli np. masz kilka implementacji SpringBootServletInitializer - np. jedną "developerską/produkcyjną", a drugą do testów etc. Jeśli nie możesz wywalić/zunifikować wielu implementacji, to uzależnij tworzenie beanów np. od profilu - @Profile("test"), @Profile("dev") itp., lub od jakichś ustawień w propertiesach i przestawiaj je do testów: @ConditionalOnProperty(name = ["my.custom.prop..testing"], havingValue = "true", matchIfMissing = false)
  • jak jeszcze nie masz, to utworzyć sobie osobny profil testowy np. test i odpowiedni config w application-test.properties - tak na zapas, żeby mieć porządek w projekcie.
  • dorzuć w pliku ze schemą bazy linijkę DROP TABLE IF EXISTS .. przed CREATE TABLE ... lub dorzuć TRUNCATE TABLE / DELETE FROM przed ładowaniem danych do bazy
  • nie powierzaj ładowania tych skryptów Spring Bootowi. Będzie to robił przy każdym starcie aplikacji, przy każdym ładowaniu kontekstu, będzie odwalał Ci różne cyrki, wyczyści bazę w której już coś masz albo wywali się, próbując utworzyć już istniejącą tabelę. Spróbuj się pobawić jakimś toolem do migracji np. Flyway, o którym już wspomniałem, albo np. Liquibase. Utwórz sobie osobne migracje dla aplikacji (schema) oraz osobne dla testów (dane testowe). Wykorzystaj narzędzia, by zapewnić sprzątanie bazy przed każdym testem, choćby tymi flyway-test-extensions, oznacz testy jako @FlywayTest jeśli zdecydujesz się na to narzędzie albo użyj odpowiednika z innych narzędzi. @FlywayTest pozwala zdefiniować dodatkowe lokalizacje dla migracji testowych - to się przydaje.

EDIT - post się troszkę zdezaktualizował przez to, że OP uprzedził moje pytania tuż przed zamieszczeniem posta, no ale już zostawię.


edytowany 2x, ostatnio: superdurszlak
superdurszlak
  • Rejestracja:ponad 6 lat
  • Ostatnio:minuta
  • Lokalizacja:Kraków
  • Postów:1999
0
Maciej Jeleń napisał(a):

@kkojot:
Dokładnie tak mam, mam oddzielny profil (application.properties i data.sql) dla testów

Czy ten skrypt jest zduplikowany i masz go w kilku miejscach? Może przy ładowaniu testów znajdowane są dwa skrypty (jeden w resourcach normalnych i jeden w testowych) i odpalane są oba, przez co przy drugim się wykłada?

Ten oddzielny profil to po prostu nadpisane application.properties, tak? To raczej powinien być całkiem oddzielny profil application-test.properties + wskazywanie na profil test przez @Profile w beanach tylko dla testów, wtedy masz większą kontrolę nad tym, co się dzieje (rzeczy testowe ładują się warunkowo, jeśli aktywny jest profil testowy itp)


MJ
Maciej Jeleń
Tak, jest dokładnie tak jak napisałeś - zduplikowany plik SQL, tylko w innej lokalizacji w strukturze i application.properties w test/resources, konfigurujące H2
superdurszlak
  • Rejestracja:ponad 6 lat
  • Ostatnio:minuta
  • Lokalizacja:Kraków
  • Postów:1999
2

Tak, jest dokładnie tak jak napisałeś - zduplikowany plik SQL, tylko w innej lokalizacji w strukturze i application.properties w test/resources, konfigurujące H2

Dla pewności wrzuć dla odmiany jakieś inne dane do testowego, niż miałeś w oryginalnym (by nie wywalił się na duplikatach) i sprawdź, czy w trakcie testu w bazie siedzą dane z obu. Obstawiam, że to będzie to :P W trakcie odpalania testów przeglądane są oba foldery resources (aplikacji i testowe), w obu znajdowane są pliki domyślnie ładowane przez Spring Boota (data.sql, schema.sql, data-platform.sql itp) więc prawdopodobnie wciągane są obydwa i gdy drugi raz ładowane są rekordy z tymi samymi emailami, wywala się constraint ;)

W ramach quick-fixa możesz zmienić nazwę skryptów z danymi na nie-defaultowe, by nie ładowały się z marszu, i wskazywać na odpowiednie skrypty explicite w kofiguracji profilu np.:

Kopiuj
spring.datasource.data=test_specific_data.sql

Do poczytania:
https://www.baeldung.com/spring-boot-sql-import-files


edytowany 5x, ostatnio: superdurszlak
MJ
MJ
  • Rejestracja:prawie 6 lat
  • Ostatnio:ponad 5 lat
  • Postów:8
0

Okej, wychodzi na to że naprawdę wykonuje się dwa razy ten plik - jeden z main/resources, drugi - z test/resources, zrobiłem tak jak @superdurszlak napisał i wszystko działą :D

Czyli mogę usunąć plik data.sql z test/resources i będzie to samo?

superdurszlak
  • Rejestracja:ponad 6 lat
  • Ostatnio:minuta
  • Lokalizacja:Kraków
  • Postów:1999
1
Maciej Jeleń napisał(a):

Czyli mogę usunąć plik data.sql z test/resources i będzie to samo?

Tak, ale lepiej będzie, jeśli skonfigurujesz sobie tak, aby:

  • normalnie żadne dane nie ładowały się do bazy przy starcie aplikacji, chyba że masz jakieś np. dane "słownikowe" które muszą być wprowadzone do bazy by aplikacja działała poprawnie - ale i tu dobrze by było skonfigurować to tak, by te skrypty nie odpalały się za każdym razem, bo po paru uruchomieniach aplikacji zrobi Ci się śmietnik. Narzędzia do migracji mogą być tu pomocne.
  • te dane testowe ładowały się wyłącznie w testach
  • wprost wskazać źródło danych dla testów, zamiast polegać na defaultach - co, jeśli inne testy będziesz chciał odpalać na zupełnie innych danych? ;)

edytowany 3x, ostatnio: superdurszlak
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)