Merge pull request #184 from jyapayne/rework_tests

Make tests nicer to write
This commit is contained in:
Dominik Picheta 2018-07-17 16:31:58 +01:00 • committed by GitHub
commit 93913d9b13
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 245 additions and 148 deletions

View file

@ -94,7 +94,7 @@ when defined(js):
proc render*(state: DeleteModal): VNode = proc render*(state: DeleteModal): VNode =
result = buildHtml(): result = buildHtml():
tdiv(class=class({"active": state.shown}, "modal modal-sm"), tdiv(class=class({"active": state.shown}, "modal modal-sm"),
id="login-modal"): id="delete-modal"):
a(href="", class="modal-overlay", "aria-label"="close", a(href="", class="modal-overlay", "aria-label"="close",
onClick=(ev: Event, n: VNode) => onClose(ev, n, state)) onClick=(ev: Event, n: VNode) => onClose(ev, n, state))
tdiv(class="modal-container"): tdiv(class="modal-container"):
@ -122,11 +122,11 @@ when defined(js):
button(class=class( button(class=class(
{"loading": state.loading}, {"loading": state.loading},
"btn btn-primary" "btn btn-primary delete-btn"
), ),
onClick=(ev: Event, n: VNode) => onDelete(ev, n, state)): onClick=(ev: Event, n: VNode) => onDelete(ev, n, state)):
italic(class="fas fa-trash-alt") italic(class="fas fa-trash-alt")
text " Delete" text " Delete"
button(class="btn", button(class="btn cancel-btn",
onClick=(ev: Event, n: VNode) => (state.shown = false)): onClick=(ev: Event, n: VNode) => (state.shown = false)):
text "Cancel" text "Cancel"

View file

@ -87,7 +87,7 @@ when defined(js):
text state.error.get().message text state.error.get().message
tdiv(class="edit-buttons"): tdiv(class="edit-buttons"):
tdiv(class="reply-button"): tdiv(class="cancel-button"):
button(class="btn btn-link", button(class="btn btn-link",
onClick=(e: Event, n: VNode) => (state.onEditCancel())): onClick=(e: Event, n: VNode) => (state.onEditCancel())):
text " Cancel" text " Cancel"

View file

@ -78,17 +78,18 @@ when defined(js):
"data-sitekey"=recaptchaSiteKey.get()) "data-sitekey"=recaptchaSiteKey.get())
script(src="https://www.google.com/recaptcha/api.js") script(src="https://www.google.com/recaptcha/api.js")
tdiv(class="modal-footer"): tdiv(class="modal-footer"):
button(class=class({"loading": state.loading}, "btn btn-primary"), button(class=class({"loading": state.loading},
onClick=(ev: Event, n: VNode) => onSignUpClick(ev, n, state)): "btn btn-primary create-account-btn"),
onClick=(ev: Event, n: VNode) => onSignUpClick(ev, n, state)):
text "Create account" text "Create account"
button(class="btn", button(class="btn login-btn",
onClick=(ev: Event, n: VNode) => onClick=(ev: Event, n: VNode) =>
(state.onLogIn(); state.shown = false)): (state.onLogIn(); state.shown = false)):
text "Log in" text "Log in"
p(class="license-text text-gray"): p(class="license-text text-gray"):
text "By registering, you agree to the " text "By registering, you agree to the "
a(href=makeUri("/about/license"), a(id="license", href=makeUri("/about/license"),
onClick=(ev: Event, n: VNode) => onClick=(ev: Event, n: VNode) =>
(state.shown = false; anchorCB(ev, n))): (state.shown = false; anchorCB(ev, n))):
text "content license" text "content license"

View file

@ -24,7 +24,7 @@ when defined(js):
proc render*(state: UserMenu, user: User): VNode = proc render*(state: UserMenu, user: User): VNode =
result = buildHtml(): result = buildHtml():
tdiv(): tdiv(id="profile-btn"):
figure(class="avatar c-hand", figure(class="avatar c-hand",
onClick=(e: Event, n: VNode) => onClick(e, n, state)): onClick=(e: Event, n: VNode) => onClick(e, n, state)):
img(src=user.avatarUrl, title=user.name) img(src=user.avatarUrl, title=user.name)
@ -52,13 +52,15 @@ when defined(js):
tdiv(class="tile-icon"): tdiv(class="tile-icon"):
img(class="avatar", src=user.avatarUrl, img(class="avatar", src=user.avatarUrl,
title=user.name) title=user.name)
tdiv(class="tile-content"): tdiv(id="profile-name", class="tile-content"):
text user.name text user.name
li(class="divider") li(class="divider")
li(class="menu-item"): li(class="menu-item"):
a(href=makeUri("/profile/" & user.name)): a(id="myprofile-btn",
href=makeUri("/profile/" & user.name)):
text "My profile" text "My profile"
li(class="menu-item c-hand"): li(class="menu-item c-hand"):
a(onClick = (e: Event, n: VNode) => a(id="logout-btn",
onClick = (e: Event, n: VNode) =>
(state.shown=false; state.onLogout())): (state.shown=false; state.onLogout())):
text "Logout" text "Logout"

View file

@ -22,10 +22,25 @@ proc backup(path: string, contents: Option[string]=none[string]()) =
echo(path, " already exists. Moving to ", backupPath) echo(path, " already exists. Moving to ", backupPath)
moveFile(path, backupPath) moveFile(path, backupPath)
proc createUser(db: DbConn, user: tuple[username, password, email: string],
rank: Rank) =
assert user.username.len != 0
let salt = makeSalt()
let password = makePassword(user.password, salt)
exec(db, sql"""
INSERT INTO person(name, password, email, salt, status, lastOnline)
VALUES (?, ?, ?, ?, ?, DATETIME('now'))
""", user.username, password, user.email, salt, $rank)
proc initialiseDb(admin: tuple[username, password, email: string], proc initialiseDb(admin: tuple[username, password, email: string],
filename="nimforum.db") = filename="nimforum.db") =
let path = getCurrentDir() / filename let
if "-dev" notin filename and "-test" notin filename: path = getCurrentDir() / filename
isTest = "-test" in filename
isDev = "-dev" in filename
if not isDev and not isTest:
backup(path) backup(path)
removeFile(path) removeFile(path)
@ -98,13 +113,16 @@ proc initialiseDb(admin: tuple[username, password, email: string],
db.exec sql"create index PersonStatusIdx on person(status);" db.exec sql"create index PersonStatusIdx on person(status);"
# Create default user. # Create default user.
if admin.username.len != 0: db.createUser(admin, Admin)
let salt = makeSalt()
let password = makePassword(admin.password, salt) # Create test users if test or development
db.exec(sql""" if isTest or isDev:
insert into person (id, name, password, email, salt, status) for rank in Spammer..Moderator:
values (1, ?, ?, ?, ?, ?); let rankLower = toLowerAscii($rank)
""", admin.username, password, admin.email, salt, $Admin) let user = (username: $rankLower,
password: $rankLower,
email: $rankLower & "@localhost.local")
db.createUser(user, rank)
# -- Post # -- Post

View file

@ -1,5 +1,57 @@
import os, options import os, options, unittest
import webdriver import webdriver
import macros
macro with*(obj: typed, code: untyped): untyped =
## Execute a set of statements with an object
expectKind code, nnkStmtList
result = code
# Simply inject obj into call
for i in 0 ..< result.len:
if result[i].kind in {nnkCommand, nnkCall}:
result[i].insert(1, obj)
template click*(session: Session, element: string, strategy=CssSelector) =
let el = session.findElement(element, strategy)
check el.isSome()
el.get().click()
template sendKeys*(session: Session, element, keys: string) =
let el = session.findElement(element)
check el.isSome()
el.get().sendKeys(keys)
template sendKeys*(session: Session, element: string, keys: varargs[Key]) =
let el = session.findElement(element)
check el.isSome()
# focus
el.get().click()
for key in keys:
session.press(key)
template ensureExists*(session: Session, element: string) =
let el = session.findElement(element)
check el.isSome()
template check*(session: Session, element: string, function: untyped) =
let el = session.findElement(element)
check function(el)
template check*(session: Session, element: string,
strategy: LocationStrategy, function: untyped) =
let el = session.findElement(element, strategy)
check function(el)
template checkIsNone*(session: Session, element: string, strategy=CssSelector) =
let el = session.findElement(element, strategy)
check el.isNone()
template checkText*(session: Session, element, expectedValue: string) =
let el = session.findElement(element)
check el.isSome()
check el.get().getText() == expectedValue
proc waitForLoad*(session: Session, timeout=20000) = proc waitForLoad*(session: Session, timeout=20000) =
var waitTime = 0 var waitTime = 0
@ -14,29 +66,23 @@ proc waitForLoad*(session: Session, timeout=20000) =
if waitTime > timeout: if waitTime > timeout:
doAssert false, "Wait for load time exceeded" doAssert false, "Wait for load time exceeded"
proc wait*(session: Session, msTimeout: int = 5000) =
session.waitForLoad(msTimeout)
proc logout*(session: Session) = proc logout*(session: Session) =
# Check whether we can log out. with session:
let logoutLink = session.findElement( wait()
"Logout", click "#profile-btn"
LinkTextSelector click "#profile-btn #logout-btn"
).get() wait()
logoutLink.click()
proc login*(session: Session, user, password: string) = proc login*(session: Session, user, password: string) =
let logIn = session.findElement("#login-btn").get() with session:
logIn.click() click "#login-btn"
let usernameField = session.findElement( sendKeys "#login-form input[name='username']", user
"#login-form input[name='username']" sendKeys "#login-form input[name='password']", password
)
let passwordField = session.findElement( sendKeys "#login-form input[name='password']", Key.Enter
"#login-form input[name='password']"
)
usernameField.get().sendKeys("admin") wait()
passwordField.get().sendKeys("admin")
passwordField.get().click() # Focus field.
session.press(Key.Enter)
waitForLoad(session, 5000)

View file

@ -9,96 +9,51 @@ proc test*(session: Session, baseUrl: string) =
# Sanity checks # Sanity checks
test "shows sign up": test "shows sign up":
let signUp = session.findElement("#signup-btn") session.checkText("#signup-btn", "Sign up")
check signUp.get().getText() == "Sign up"
test "shows log in": test "shows log in":
let logIn = session.findElement("#login-btn") session.checkText("#login-btn", "Log in")
check logIn.get().getText() == "Log in"
test "is empty": test "is empty":
let thread = session.findElement("tr > td.thread-title") session.checkIsNone("tr > td.thread-title")
check thread.isNone()
# Logging in # Logging in
test "can login/logout": test "can login/logout":
let logIn = session.findElement("#login-btn").get() with session:
logIn.click() click "#login-btn"
let usernameField = session.findElement( sendKeys "#login-form input[name='username']", "admin"
"#login-form input[name='username']" sendKeys "#login-form input[name='password']", "admin"
)
check usernameField.isSome()
let passwordField = session.findElement(
"#login-form input[name='password']"
)
check passwordField.isSome()
usernameField.get().sendKeys("admin") sendKeys "#login-form input[name='password']", Key.Enter
passwordField.get().sendKeys("admin") wait()
passwordField.get().click() # Focus field.
session.press(Key.Enter)
waitForLoad(session, 5000) # Verify that the user menu has been initialised properly.
click "#profile-btn"
checkText "#profile-btn #profile-name", "admin"
# Verify that the user menu has been initialised properly. # Check whether we can log out.
let profileButton = session.findElement( click "#logout-btn"
"#main-navbar figure.avatar" # Verify we have logged out by looking for the log in button.
).get() ensureExists "#login-btn"
profileButton.click()
let profileName = session.findElement(
"#main-navbar .menu-right div.tile-content"
).get()
check profileName.getText() == "admin"
# Check whether we can log out.
let logoutLink = session.findElement(
"Logout",
LinkTextSelector
).get()
logoutLink.click()
# Verify we have logged out by looking for the log in button.
check session.findElement("#login-btn").isSome()
test "can register": test "can register":
let signup = session.findElement("#signup-btn").get() with session:
signup.click() click "#signup-btn"
let emailField = session.findElement( sendKeys "#signup-form input[name='email']", "test@test.com"
"#signup-form input[name='email']" sendKeys "#signup-form input[name='username']", "test"
).get() sendKeys "#signup-form input[name='password']", "test"
let usernameField = session.findElement(
"#signup-form input[name='username']"
).get()
let passwordField = session.findElement(
"#signup-form input[name='password']"
).get()
emailField.sendKeys("test@test.com") click "#signup-modal .create-account-btn"
usernameField.sendKeys("test") wait()
passwordField.sendKeys("test")
let createAccount = session.findElement( # Verify that the user menu has been initialised properly.
"#signup-modal .modal-footer .btn-primary" click "#profile-btn"
).get() checkText "#profile-btn #profile-name", "test"
# close menu
createAccount.click() click "#profile-btn"
waitForLoad(session, 5000)
# Verify that the user menu has been initialised properly.
let profileButton = session.findElement(
"#main-navbar figure.avatar"
).get()
profileButton.click()
let profileName = session.findElement(
"#main-navbar .menu-right div.tile-content"
).get()
check profileName.getText() == "test"
session.navigate(baseUrl)
session.wait()
logout(session) logout(session)

View file

@ -3,44 +3,119 @@ import unittest, options, os, common
import webdriver import webdriver
proc test*(session: Session, baseUrl: string) = proc test*(session: Session, baseUrl: string) =
session.navigate(baseUrl) let
userTitleStr = "This is a user thread!"
userContentStr = "A user has filled this out"
waitForLoad(session) adminTitleStr = "This is a thread title!"
adminContentStr = "This is content"
login(session, "admin", "admin") suite "user thread tests":
session.navigate(baseUrl)
session.wait()
login(session, "user", "user")
test "can create thread": setup:
let newThreadBtn = session.findElement("#new-thread-btn").get() session.navigate(baseUrl)
newThreadBtn.click() session.wait()
waitForLoad(session) test "can create thread":
with session:
click "#new-thread-btn"
wait()
let newThread = session.findElement("#new-thread") sendKeys "#thread-title", userTitleStr
check newThread.isSome() sendKeys "#reply-textarea", userContentStr
let createThreadBtn = session.findElement("#create-thread-btn") click "#create-thread-btn"
check createThreadBtn.isSome() wait()
checkText "#thread-title", userTitleStr
checkText ".original-post div.post-content", userContentStr
let threadTitle = session.findElement("#thread-title") session.navigate(baseUrl)
check threadTitle.isSome() session.wait()
logout(session)
let replyBox = session.findElement("#reply-textarea") suite "admin thread tests":
check replyBox.isSome() session.navigate(baseUrl)
session.wait()
login(session, "admin", "admin")
threadTitle.get().sendKeys("This is a thread title!") setup:
replyBox.get().sendKeys("This is content.") session.navigate(baseUrl)
session.wait()
createThreadBtn.get().click() test "can create thread":
with session:
click "#new-thread-btn"
wait()
waitForLoad(session) sendKeys "#thread-title", adminTitleStr
sendKeys "#reply-textarea", adminContentStr
let newThreadTitle = session.findElement("#thread-title") click "#create-thread-btn"
check newThreadTitle.isSome() wait()
check newThreadTitle.get().getText() == "This is a thread title!" checkText "#thread-title", adminTitleStr
checkText ".original-post div.post-content", adminContentStr
let content = session.findElement(".original-post div.post-content") test "try create duplicate thread":
check content.isSome() with session:
click "#new-thread-btn"
wait()
ensureExists "#new-thread"
check content.get().getText() == "This is content." sendKeys "#thread-title", adminTitleStr
sendKeys "#reply-textarea", adminContentStr
click "#create-thread-btn"
wait()
ensureExists "#new-thread p.text-error"
test "can edit post":
let modificationText = " and I edited it!"
with session:
click adminTitleStr, LinkTextSelector
wait()
click ".post-buttons .edit-button"
wait()
sendKeys ".original-post #reply-textarea", modificationText
click ".edit-buttons .save-button"
wait()
checkText ".original-post div.post-content", adminContentStr & modificationText
test "can like thread":
# Try to like the user thread above
with session:
click userTitleStr, LinkTextSelector
wait()
click ".post-buttons .like-button"
checkText ".post-buttons .like-button .like-count", "1"
test "can delete thread":
with session:
click adminTitleStr, LinkTextSelector
wait()
click ".post-buttons .delete-button"
wait()
# click delete confirmation
click "#delete-modal .delete-btn"
wait()
# Make sure the forum post is gone
checkIsNone adminTitleStr, LinkTextSelector
session.navigate(baseUrl)
session.wait()
logout(session)