Merge pull request #184 from jyapayne/rework_tests
Make tests nicer to write
This commit is contained in:
commit
93913d9b13
9 changed files with 245 additions and 148 deletions
|
|
@ -94,7 +94,7 @@ when defined(js):
|
|||
proc render*(state: DeleteModal): VNode =
|
||||
result = buildHtml():
|
||||
tdiv(class=class({"active": state.shown}, "modal modal-sm"),
|
||||
id="login-modal"):
|
||||
id="delete-modal"):
|
||||
a(href="", class="modal-overlay", "aria-label"="close",
|
||||
onClick=(ev: Event, n: VNode) => onClose(ev, n, state))
|
||||
tdiv(class="modal-container"):
|
||||
|
|
@ -122,11 +122,11 @@ when defined(js):
|
|||
|
||||
button(class=class(
|
||||
{"loading": state.loading},
|
||||
"btn btn-primary"
|
||||
"btn btn-primary delete-btn"
|
||||
),
|
||||
onClick=(ev: Event, n: VNode) => onDelete(ev, n, state)):
|
||||
italic(class="fas fa-trash-alt")
|
||||
text " Delete"
|
||||
button(class="btn",
|
||||
button(class="btn cancel-btn",
|
||||
onClick=(ev: Event, n: VNode) => (state.shown = false)):
|
||||
text "Cancel"
|
||||
text "Cancel"
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ when defined(js):
|
|||
text state.error.get().message
|
||||
|
||||
tdiv(class="edit-buttons"):
|
||||
tdiv(class="reply-button"):
|
||||
tdiv(class="cancel-button"):
|
||||
button(class="btn btn-link",
|
||||
onClick=(e: Event, n: VNode) => (state.onEditCancel())):
|
||||
text " Cancel"
|
||||
|
|
@ -95,4 +95,4 @@ when defined(js):
|
|||
button(class=class({"loading": state.loading}, "btn btn-primary"),
|
||||
onClick=(e: Event, n: VNode) => state.save()):
|
||||
italic(class="fas fa-check")
|
||||
text " Save"
|
||||
text " Save"
|
||||
|
|
|
|||
|
|
@ -149,4 +149,4 @@ proc render(): VNode =
|
|||
])
|
||||
|
||||
window.onPopState = onPopState
|
||||
setRenderer render
|
||||
setRenderer render
|
||||
|
|
|
|||
|
|
@ -78,18 +78,19 @@ when defined(js):
|
|||
"data-sitekey"=recaptchaSiteKey.get())
|
||||
script(src="https://www.google.com/recaptcha/api.js")
|
||||
tdiv(class="modal-footer"):
|
||||
button(class=class({"loading": state.loading}, "btn btn-primary"),
|
||||
onClick=(ev: Event, n: VNode) => onSignUpClick(ev, n, state)):
|
||||
button(class=class({"loading": state.loading},
|
||||
"btn btn-primary create-account-btn"),
|
||||
onClick=(ev: Event, n: VNode) => onSignUpClick(ev, n, state)):
|
||||
text "Create account"
|
||||
button(class="btn",
|
||||
button(class="btn login-btn",
|
||||
onClick=(ev: Event, n: VNode) =>
|
||||
(state.onLogIn(); state.shown = false)):
|
||||
text "Log in"
|
||||
|
||||
p(class="license-text text-gray"):
|
||||
text "By registering, you agree to the "
|
||||
a(href=makeUri("/about/license"),
|
||||
a(id="license", href=makeUri("/about/license"),
|
||||
onClick=(ev: Event, n: VNode) =>
|
||||
(state.shown = false; anchorCB(ev, n))):
|
||||
text "content license"
|
||||
text "."
|
||||
text "."
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ when defined(js):
|
|||
|
||||
proc render*(state: UserMenu, user: User): VNode =
|
||||
result = buildHtml():
|
||||
tdiv():
|
||||
tdiv(id="profile-btn"):
|
||||
figure(class="avatar c-hand",
|
||||
onClick=(e: Event, n: VNode) => onClick(e, n, state)):
|
||||
img(src=user.avatarUrl, title=user.name)
|
||||
|
|
@ -52,13 +52,15 @@ when defined(js):
|
|||
tdiv(class="tile-icon"):
|
||||
img(class="avatar", src=user.avatarUrl,
|
||||
title=user.name)
|
||||
tdiv(class="tile-content"):
|
||||
tdiv(id="profile-name", class="tile-content"):
|
||||
text user.name
|
||||
li(class="divider")
|
||||
li(class="menu-item"):
|
||||
a(href=makeUri("/profile/" & user.name)):
|
||||
a(id="myprofile-btn",
|
||||
href=makeUri("/profile/" & user.name)):
|
||||
text "My profile"
|
||||
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())):
|
||||
text "Logout"
|
||||
text "Logout"
|
||||
|
|
|
|||
|
|
@ -22,10 +22,25 @@ proc backup(path: string, contents: Option[string]=none[string]()) =
|
|||
echo(path, " already exists. Moving to ", 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],
|
||||
filename="nimforum.db") =
|
||||
let path = getCurrentDir() / filename
|
||||
if "-dev" notin filename and "-test" notin filename:
|
||||
let
|
||||
path = getCurrentDir() / filename
|
||||
isTest = "-test" in filename
|
||||
isDev = "-dev" in filename
|
||||
|
||||
if not isDev and not isTest:
|
||||
backup(path)
|
||||
|
||||
removeFile(path)
|
||||
|
|
@ -98,13 +113,16 @@ proc initialiseDb(admin: tuple[username, password, email: string],
|
|||
db.exec sql"create index PersonStatusIdx on person(status);"
|
||||
|
||||
# Create default user.
|
||||
if admin.username.len != 0:
|
||||
let salt = makeSalt()
|
||||
let password = makePassword(admin.password, salt)
|
||||
db.exec(sql"""
|
||||
insert into person (id, name, password, email, salt, status)
|
||||
values (1, ?, ?, ?, ?, ?);
|
||||
""", admin.username, password, admin.email, salt, $Admin)
|
||||
db.createUser(admin, Admin)
|
||||
|
||||
# Create test users if test or development
|
||||
if isTest or isDev:
|
||||
for rank in Spammer..Moderator:
|
||||
let rankLower = toLowerAscii($rank)
|
||||
let user = (username: $rankLower,
|
||||
password: $rankLower,
|
||||
email: $rankLower & "@localhost.local")
|
||||
db.createUser(user, rank)
|
||||
|
||||
# -- Post
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,57 @@
|
|||
import os, options
|
||||
import os, options, unittest
|
||||
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) =
|
||||
var waitTime = 0
|
||||
|
|
@ -14,29 +66,23 @@ proc waitForLoad*(session: Session, timeout=20000) =
|
|||
if waitTime > timeout:
|
||||
doAssert false, "Wait for load time exceeded"
|
||||
|
||||
proc wait*(session: Session, msTimeout: int = 5000) =
|
||||
session.waitForLoad(msTimeout)
|
||||
|
||||
proc logout*(session: Session) =
|
||||
# Check whether we can log out.
|
||||
let logoutLink = session.findElement(
|
||||
"Logout",
|
||||
LinkTextSelector
|
||||
).get()
|
||||
logoutLink.click()
|
||||
with session:
|
||||
wait()
|
||||
click "#profile-btn"
|
||||
click "#profile-btn #logout-btn"
|
||||
wait()
|
||||
|
||||
proc login*(session: Session, user, password: string) =
|
||||
let logIn = session.findElement("#login-btn").get()
|
||||
logIn.click()
|
||||
with session:
|
||||
click "#login-btn"
|
||||
|
||||
let usernameField = session.findElement(
|
||||
"#login-form input[name='username']"
|
||||
)
|
||||
sendKeys "#login-form input[name='username']", user
|
||||
sendKeys "#login-form input[name='password']", password
|
||||
|
||||
let passwordField = session.findElement(
|
||||
"#login-form input[name='password']"
|
||||
)
|
||||
sendKeys "#login-form input[name='password']", Key.Enter
|
||||
|
||||
usernameField.get().sendKeys("admin")
|
||||
passwordField.get().sendKeys("admin")
|
||||
passwordField.get().click() # Focus field.
|
||||
session.press(Key.Enter)
|
||||
|
||||
waitForLoad(session, 5000)
|
||||
wait()
|
||||
|
|
|
|||
|
|
@ -9,96 +9,51 @@ proc test*(session: Session, baseUrl: string) =
|
|||
|
||||
# Sanity checks
|
||||
test "shows sign up":
|
||||
let signUp = session.findElement("#signup-btn")
|
||||
check signUp.get().getText() == "Sign up"
|
||||
session.checkText("#signup-btn", "Sign up")
|
||||
|
||||
test "shows log in":
|
||||
let logIn = session.findElement("#login-btn")
|
||||
check logIn.get().getText() == "Log in"
|
||||
session.checkText("#login-btn", "Log in")
|
||||
|
||||
test "is empty":
|
||||
let thread = session.findElement("tr > td.thread-title")
|
||||
check thread.isNone()
|
||||
session.checkIsNone("tr > td.thread-title")
|
||||
|
||||
# Logging in
|
||||
test "can login/logout":
|
||||
let logIn = session.findElement("#login-btn").get()
|
||||
logIn.click()
|
||||
with session:
|
||||
click "#login-btn"
|
||||
|
||||
let usernameField = session.findElement(
|
||||
"#login-form input[name='username']"
|
||||
)
|
||||
check usernameField.isSome()
|
||||
let passwordField = session.findElement(
|
||||
"#login-form input[name='password']"
|
||||
)
|
||||
check passwordField.isSome()
|
||||
sendKeys "#login-form input[name='username']", "admin"
|
||||
sendKeys "#login-form input[name='password']", "admin"
|
||||
|
||||
usernameField.get().sendKeys("admin")
|
||||
passwordField.get().sendKeys("admin")
|
||||
passwordField.get().click() # Focus field.
|
||||
session.press(Key.Enter)
|
||||
sendKeys "#login-form input[name='password']", Key.Enter
|
||||
wait()
|
||||
|
||||
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.
|
||||
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() == "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()
|
||||
# Check whether we can log out.
|
||||
click "#logout-btn"
|
||||
# Verify we have logged out by looking for the log in button.
|
||||
ensureExists "#login-btn"
|
||||
|
||||
test "can register":
|
||||
let signup = session.findElement("#signup-btn").get()
|
||||
signup.click()
|
||||
with session:
|
||||
click "#signup-btn"
|
||||
|
||||
let emailField = session.findElement(
|
||||
"#signup-form input[name='email']"
|
||||
).get()
|
||||
let usernameField = session.findElement(
|
||||
"#signup-form input[name='username']"
|
||||
).get()
|
||||
let passwordField = session.findElement(
|
||||
"#signup-form input[name='password']"
|
||||
).get()
|
||||
sendKeys "#signup-form input[name='email']", "test@test.com"
|
||||
sendKeys "#signup-form input[name='username']", "test"
|
||||
sendKeys "#signup-form input[name='password']", "test"
|
||||
|
||||
emailField.sendKeys("test@test.com")
|
||||
usernameField.sendKeys("test")
|
||||
passwordField.sendKeys("test")
|
||||
click "#signup-modal .create-account-btn"
|
||||
wait()
|
||||
|
||||
let createAccount = session.findElement(
|
||||
"#signup-modal .modal-footer .btn-primary"
|
||||
).get()
|
||||
# Verify that the user menu has been initialised properly.
|
||||
click "#profile-btn"
|
||||
checkText "#profile-btn #profile-name", "test"
|
||||
# close menu
|
||||
click "#profile-btn"
|
||||
|
||||
createAccount.click()
|
||||
|
||||
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"
|
||||
|
||||
logout(session)
|
||||
session.navigate(baseUrl)
|
||||
session.wait()
|
||||
logout(session)
|
||||
|
|
|
|||
|
|
@ -3,44 +3,119 @@ import unittest, options, os, common
|
|||
import webdriver
|
||||
|
||||
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":
|
||||
let newThreadBtn = session.findElement("#new-thread-btn").get()
|
||||
newThreadBtn.click()
|
||||
setup:
|
||||
session.navigate(baseUrl)
|
||||
session.wait()
|
||||
|
||||
waitForLoad(session)
|
||||
test "can create thread":
|
||||
with session:
|
||||
click "#new-thread-btn"
|
||||
wait()
|
||||
|
||||
let newThread = session.findElement("#new-thread")
|
||||
check newThread.isSome()
|
||||
sendKeys "#thread-title", userTitleStr
|
||||
sendKeys "#reply-textarea", userContentStr
|
||||
|
||||
let createThreadBtn = session.findElement("#create-thread-btn")
|
||||
check createThreadBtn.isSome()
|
||||
click "#create-thread-btn"
|
||||
wait()
|
||||
|
||||
checkText "#thread-title", userTitleStr
|
||||
checkText ".original-post div.post-content", userContentStr
|
||||
|
||||
let threadTitle = session.findElement("#thread-title")
|
||||
check threadTitle.isSome()
|
||||
session.navigate(baseUrl)
|
||||
session.wait()
|
||||
logout(session)
|
||||
|
||||
let replyBox = session.findElement("#reply-textarea")
|
||||
check replyBox.isSome()
|
||||
suite "admin thread tests":
|
||||
session.navigate(baseUrl)
|
||||
session.wait()
|
||||
login(session, "admin", "admin")
|
||||
|
||||
threadTitle.get().sendKeys("This is a thread title!")
|
||||
replyBox.get().sendKeys("This is content.")
|
||||
setup:
|
||||
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")
|
||||
check newThreadTitle.isSome()
|
||||
click "#create-thread-btn"
|
||||
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")
|
||||
check content.isSome()
|
||||
test "try create duplicate thread":
|
||||
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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue