Compare commits
7 commits
master
...
github_act
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e896ddd65a | ||
|
|
11ee8b16e7 |
||
|
|
17e5cbaa29 |
||
|
|
a8e577e2f3 |
||
|
|
91eae347d5 |
||
|
|
7da8f58210 |
||
|
|
5e92ff28ac |
11 changed files with 25 additions and 225 deletions
|
|
@ -276,7 +276,7 @@ template createTFD() =
|
||||||
new(c)
|
new(c)
|
||||||
init(c)
|
init(c)
|
||||||
c.req = request
|
c.req = request
|
||||||
if cookies(request).len > 0:
|
if request.cookies.len > 0:
|
||||||
checkLoggedIn(c)
|
checkLoggedIn(c)
|
||||||
|
|
||||||
#[ DB functions. TODO: Move to another module? ]#
|
#[ DB functions. TODO: Move to another module? ]#
|
||||||
|
|
@ -400,10 +400,10 @@ proc selectThread(threadRow: seq[string], author: User): Thread =
|
||||||
id: threadRow[0].parseInt,
|
id: threadRow[0].parseInt,
|
||||||
topic: threadRow[1],
|
topic: threadRow[1],
|
||||||
category: Category(
|
category: Category(
|
||||||
id: threadRow[6].parseInt,
|
id: threadRow[5].parseInt,
|
||||||
name: threadRow[7],
|
name: threadRow[6],
|
||||||
description: threadRow[8],
|
description: threadRow[7],
|
||||||
color: threadRow[9]
|
color: threadRow[8]
|
||||||
),
|
),
|
||||||
users: @[],
|
users: @[],
|
||||||
replies: posts[0].parseInt-1,
|
replies: posts[0].parseInt-1,
|
||||||
|
|
@ -412,7 +412,6 @@ proc selectThread(threadRow: seq[string], author: User): Thread =
|
||||||
creation: posts[1].parseInt,
|
creation: posts[1].parseInt,
|
||||||
isLocked: threadRow[4] == "1",
|
isLocked: threadRow[4] == "1",
|
||||||
isSolved: false, # TODO: Add a field to `post` to identify the solution.
|
isSolved: false, # TODO: Add a field to `post` to identify the solution.
|
||||||
isPinned: threadRow[5] == "1"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Gather the users list.
|
# Gather the users list.
|
||||||
|
|
@ -710,13 +709,6 @@ proc executeLockState(c: TForumData, threadId: int, locked: bool) =
|
||||||
# Save the like.
|
# Save the like.
|
||||||
exec(db, crud(crUpdate, "thread", "isLocked"), locked.int, threadId)
|
exec(db, crud(crUpdate, "thread", "isLocked"), locked.int, threadId)
|
||||||
|
|
||||||
proc executePinState(c: TForumData, threadId: int, pinned: bool) =
|
|
||||||
if c.rank < Moderator:
|
|
||||||
raise newForumError("You do not have permission to pin this thread.")
|
|
||||||
|
|
||||||
# (Un)pin this thread
|
|
||||||
exec(db, crud(crUpdate, "thread", "isPinned"), pinned.int, threadId)
|
|
||||||
|
|
||||||
proc executeDeletePost(c: TForumData, postId: int) =
|
proc executeDeletePost(c: TForumData, postId: int) =
|
||||||
# Verify that this post belongs to the user.
|
# Verify that this post belongs to the user.
|
||||||
const postQuery = sql"""
|
const postQuery = sql"""
|
||||||
|
|
@ -841,7 +833,7 @@ routes:
|
||||||
categoryArgs.insert($categoryId, 0)
|
categoryArgs.insert($categoryId, 0)
|
||||||
|
|
||||||
const threadsQuery =
|
const threadsQuery =
|
||||||
"""select t.id, t.name, views, strftime('%s', modified), isLocked, isPinned,
|
"""select t.id, t.name, views, strftime('%s', modified), isLocked,
|
||||||
c.id, c.name, c.description, c.color,
|
c.id, c.name, c.description, c.color,
|
||||||
u.id, u.name, u.email, strftime('%s', u.lastOnline),
|
u.id, u.name, u.email, strftime('%s', u.lastOnline),
|
||||||
strftime('%s', u.previousVisitAt), u.status, u.isDeleted
|
strftime('%s', u.previousVisitAt), u.status, u.isDeleted
|
||||||
|
|
@ -854,14 +846,14 @@ routes:
|
||||||
order by p.author
|
order by p.author
|
||||||
limit 1
|
limit 1
|
||||||
)
|
)
|
||||||
order by isPinned desc, modified desc limit ?, ?;"""
|
order by modified desc limit ?, ?;"""
|
||||||
|
|
||||||
let thrCount = getValue(db, countQuery, countArgs).parseInt()
|
let thrCount = getValue(db, countQuery, countArgs).parseInt()
|
||||||
let moreCount = max(0, thrCount - (start + count))
|
let moreCount = max(0, thrCount - (start + count))
|
||||||
|
|
||||||
var list = ThreadList(threads: @[], moreCount: moreCount)
|
var list = ThreadList(threads: @[], moreCount: moreCount)
|
||||||
for data in getAllRows(db, sql(threadsQuery % categorySection), categoryArgs):
|
for data in getAllRows(db, sql(threadsQuery % categorySection), categoryArgs):
|
||||||
let thread = selectThread(data[0 .. 9], selectUser(data[10 .. ^1]))
|
let thread = selectThread(data[0 .. 8], selectUser(data[9 .. ^1]))
|
||||||
list.threads.add(thread)
|
list.threads.add(thread)
|
||||||
|
|
||||||
resp $(%list), "application/json"
|
resp $(%list), "application/json"
|
||||||
|
|
@ -876,17 +868,12 @@ routes:
|
||||||
count = 10
|
count = 10
|
||||||
|
|
||||||
const threadsQuery =
|
const threadsQuery =
|
||||||
sql"""select t.id, t.name, views, strftime('%s', modified), isLocked, isPinned,
|
sql"""select t.id, t.name, views, strftime('%s', modified), isLocked,
|
||||||
c.id, c.name, c.description, c.color
|
c.id, c.name, c.description, c.color
|
||||||
from thread t, category c
|
from thread t, category c
|
||||||
where t.id = ? and isDeleted = 0 and category = c.id;"""
|
where t.id = ? and isDeleted = 0 and category = c.id;"""
|
||||||
|
|
||||||
let threadRow = getRow(db, threadsQuery, id)
|
let threadRow = getRow(db, threadsQuery, id)
|
||||||
if threadRow[0].len == 0:
|
|
||||||
let err = PostError(
|
|
||||||
message: "Specified thread does not exist"
|
|
||||||
)
|
|
||||||
resp Http404, $(%err), "application/json"
|
|
||||||
let thread = selectThread(threadRow, selectThreadAuthor(id))
|
let thread = selectThread(threadRow, selectThreadAuthor(id))
|
||||||
|
|
||||||
let postsQuery =
|
let postsQuery =
|
||||||
|
|
@ -932,14 +919,9 @@ routes:
|
||||||
|
|
||||||
get "/specific_posts.json":
|
get "/specific_posts.json":
|
||||||
createTFD()
|
createTFD()
|
||||||
var ids: JsonNode
|
var
|
||||||
try:
|
|
||||||
ids = parseJson(@"ids")
|
ids = parseJson(@"ids")
|
||||||
except JsonParsingError:
|
|
||||||
let err = PostError(
|
|
||||||
message: "Invalid JSON in the `ids` parameter"
|
|
||||||
)
|
|
||||||
resp Http400, $(%err), "application/json"
|
|
||||||
cond ids.kind == JArray
|
cond ids.kind == JArray
|
||||||
let intIDs = ids.elems.map(x => x.getInt())
|
let intIDs = ids.elems.map(x => x.getInt())
|
||||||
let postsQuery = sql("""
|
let postsQuery = sql("""
|
||||||
|
|
@ -1357,33 +1339,6 @@ routes:
|
||||||
except ForumError as exc:
|
except ForumError as exc:
|
||||||
resp Http400, $(%exc.data), "application/json"
|
resp Http400, $(%exc.data), "application/json"
|
||||||
|
|
||||||
post re"/(pin|unpin)":
|
|
||||||
createTFD()
|
|
||||||
if not c.loggedIn():
|
|
||||||
let err = PostError(
|
|
||||||
errorFields: @[],
|
|
||||||
message: "Not logged in."
|
|
||||||
)
|
|
||||||
resp Http401, $(%err), "application/json"
|
|
||||||
|
|
||||||
let formData = request.formData
|
|
||||||
cond "id" in formData
|
|
||||||
|
|
||||||
let threadId = getInt(formData["id"].body, -1)
|
|
||||||
cond threadId != -1
|
|
||||||
|
|
||||||
try:
|
|
||||||
case request.path
|
|
||||||
of "/pin":
|
|
||||||
executePinState(c, threadId, true)
|
|
||||||
of "/unpin":
|
|
||||||
executePinState(c, threadId, false)
|
|
||||||
else:
|
|
||||||
assert false
|
|
||||||
resp Http200, "{}", "application/json"
|
|
||||||
except ForumError as exc:
|
|
||||||
resp Http400, $(%exc.data), "application/json"
|
|
||||||
|
|
||||||
post re"/delete(Post|Thread)":
|
post re"/delete(Post|Thread)":
|
||||||
createTFD()
|
createTFD()
|
||||||
if not c.loggedIn():
|
if not c.loggedIn():
|
||||||
|
|
|
||||||
|
|
@ -96,8 +96,8 @@ when defined(js):
|
||||||
section(class="navbar-section"):
|
section(class="navbar-section"):
|
||||||
tdiv(class="input-group input-inline"):
|
tdiv(class="input-group input-inline"):
|
||||||
input(class="search-input input-sm",
|
input(class="search-input input-sm",
|
||||||
`type`="search", placeholder="Search",
|
`type`="text", placeholder="search",
|
||||||
id="search-box", required="required",
|
id="search-box",
|
||||||
onKeyDown=onKeyDown)
|
onKeyDown=onKeyDown)
|
||||||
if state.loading:
|
if state.loading:
|
||||||
tdiv(class="loading")
|
tdiv(class="loading")
|
||||||
|
|
|
||||||
|
|
@ -64,4 +64,4 @@ when defined(js):
|
||||||
renderPostUrl(thread.id, post.id)
|
renderPostUrl(thread.id, post.id)
|
||||||
|
|
||||||
proc renderPostUrl*(link: PostLink): string =
|
proc renderPostUrl*(link: PostLink): string =
|
||||||
renderPostUrl(link.threadId, link.postId)
|
renderPostUrl(link.threadId, link.postId)
|
||||||
|
|
@ -190,7 +190,7 @@ when defined(js):
|
||||||
else: ""
|
else: ""
|
||||||
|
|
||||||
result = buildHtml():
|
result = buildHtml():
|
||||||
button(class="btn btn-secondary", id="lock-btn",
|
button(class="btn btn-secondary",
|
||||||
onClick=(e: Event, n: VNode) =>
|
onClick=(e: Event, n: VNode) =>
|
||||||
onLockClick(e, n, state, thread),
|
onLockClick(e, n, state, thread),
|
||||||
"data-tooltip"=tooltip,
|
"data-tooltip"=tooltip,
|
||||||
|
|
@ -201,61 +201,4 @@ when defined(js):
|
||||||
text " Unlock Thread"
|
text " Unlock Thread"
|
||||||
else:
|
else:
|
||||||
italic(class="fas fa-lock")
|
italic(class="fas fa-lock")
|
||||||
text " Lock Thread"
|
text " Lock Thread"
|
||||||
|
|
||||||
type
|
|
||||||
PinButton* = ref object
|
|
||||||
error: Option[PostError]
|
|
||||||
loading: bool
|
|
||||||
|
|
||||||
proc newPinButton*(): PinButton =
|
|
||||||
PinButton()
|
|
||||||
|
|
||||||
proc onPost(httpStatus: int, response: kstring, state: PinButton,
|
|
||||||
thread: var Thread) =
|
|
||||||
postFinished:
|
|
||||||
thread.isPinned = not thread.isPinned
|
|
||||||
|
|
||||||
proc onPinClick(ev: Event, n: VNode, state: PinButton, thread: var Thread) =
|
|
||||||
if state.loading: return
|
|
||||||
|
|
||||||
state.loading = true
|
|
||||||
state.error = none[PostError]()
|
|
||||||
|
|
||||||
# Same as LockButton so the following is still a hack and karax should support this.
|
|
||||||
var formData = newFormData()
|
|
||||||
formData.append("id", $thread.id)
|
|
||||||
let uri =
|
|
||||||
if thread.isPinned:
|
|
||||||
makeUri("/unpin")
|
|
||||||
else:
|
|
||||||
makeUri("/pin")
|
|
||||||
ajaxPost(uri, @[], formData.to(cstring),
|
|
||||||
(s: int, r: kstring) => onPost(s, r, state, thread))
|
|
||||||
|
|
||||||
ev.preventDefault()
|
|
||||||
|
|
||||||
proc render*(state: PinButton, thread: var Thread,
|
|
||||||
currentUser: Option[User]): VNode =
|
|
||||||
if currentUser.isNone() or
|
|
||||||
currentUser.get().rank < Moderator:
|
|
||||||
return buildHtml(tdiv())
|
|
||||||
|
|
||||||
let tooltip =
|
|
||||||
if state.error.isSome(): state.error.get().message
|
|
||||||
else: ""
|
|
||||||
|
|
||||||
result = buildHtml():
|
|
||||||
button(class="btn btn-secondary", id="pin-btn",
|
|
||||||
onClick=(e: Event, n: VNode) =>
|
|
||||||
onPinClick(e, n, state, thread),
|
|
||||||
"data-tooltip"=tooltip,
|
|
||||||
onmouseleave=(e: Event, n: VNode) =>
|
|
||||||
(state.error = none[PostError]())):
|
|
||||||
if thread.isPinned:
|
|
||||||
italic(class="fas fa-thumbtack")
|
|
||||||
text " Unpin Thread"
|
|
||||||
else:
|
|
||||||
italic(class="fas fa-thumbtack")
|
|
||||||
text " Pin Thread"
|
|
||||||
|
|
||||||
|
|
@ -36,7 +36,6 @@ when defined(js):
|
||||||
likeButton: LikeButton
|
likeButton: LikeButton
|
||||||
deleteModal: DeleteModal
|
deleteModal: DeleteModal
|
||||||
lockButton: LockButton
|
lockButton: LockButton
|
||||||
pinButton: PinButton
|
|
||||||
categoryPicker: CategoryPicker
|
categoryPicker: CategoryPicker
|
||||||
|
|
||||||
proc onReplyPosted(id: int)
|
proc onReplyPosted(id: int)
|
||||||
|
|
@ -57,7 +56,6 @@ when defined(js):
|
||||||
likeButton: newLikeButton(),
|
likeButton: newLikeButton(),
|
||||||
deleteModal: newDeleteModal(onDeletePost, onDeleteThread, nil),
|
deleteModal: newDeleteModal(onDeletePost, onDeleteThread, nil),
|
||||||
lockButton: newLockButton(),
|
lockButton: newLockButton(),
|
||||||
pinButton: newPinButton(),
|
|
||||||
categoryPicker: newCategoryPicker(onCategoryChanged)
|
categoryPicker: newCategoryPicker(onCategoryChanged)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -413,7 +411,6 @@ when defined(js):
|
||||||
text " Reply"
|
text " Reply"
|
||||||
|
|
||||||
render(state.lockButton, list.thread, currentUser)
|
render(state.lockButton, list.thread, currentUser)
|
||||||
render(state.pinButton, list.thread, currentUser)
|
|
||||||
|
|
||||||
render(state.replyBox, list.thread, state.replyingTo, false)
|
render(state.replyBox, list.thread, state.replyingTo, false)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ type
|
||||||
creation*: int64 ## Unix timestamp
|
creation*: int64 ## Unix timestamp
|
||||||
isLocked*: bool
|
isLocked*: bool
|
||||||
isSolved*: bool
|
isSolved*: bool
|
||||||
isPinned*: bool
|
|
||||||
|
|
||||||
ThreadList* = ref object
|
ThreadList* = ref object
|
||||||
threads*: seq[Thread]
|
threads*: seq[Thread]
|
||||||
|
|
@ -97,18 +96,15 @@ when defined(js):
|
||||||
else:
|
else:
|
||||||
return $duration.inSeconds & "s"
|
return $duration.inSeconds & "s"
|
||||||
|
|
||||||
proc genThread(pos: int, thread: Thread, isNew: bool, noBorder: bool, displayCategory=true): VNode =
|
proc genThread(thread: Thread, isNew: bool, noBorder: bool, displayCategory=true): VNode =
|
||||||
let isOld = (getTime() - thread.creation.fromUnix).inWeeks > 2
|
let isOld = (getTime() - thread.creation.fromUnix).inWeeks > 2
|
||||||
let isBanned = thread.author.rank.isBanned()
|
let isBanned = thread.author.rank.isBanned()
|
||||||
result = buildHtml():
|
result = buildHtml():
|
||||||
tr(class=class({"no-border": noBorder, "banned": isBanned, "pinned": thread.isPinned, "thread-" & $pos: true})):
|
tr(class=class({"no-border": noBorder, "banned": isBanned})):
|
||||||
td(class="thread-title"):
|
td(class="thread-title"):
|
||||||
if thread.isLocked:
|
if thread.isLocked:
|
||||||
italic(class="fas fa-lock fa-xs",
|
italic(class="fas fa-lock fa-xs",
|
||||||
title="Thread cannot be replied to")
|
title="Thread cannot be replied to")
|
||||||
if thread.isPinned:
|
|
||||||
italic(class="fas fa-thumbtack fa-xs",
|
|
||||||
title="Pinned post")
|
|
||||||
if isBanned:
|
if isBanned:
|
||||||
italic(class="fas fa-ban fa-xs",
|
italic(class="fas fa-ban fa-xs",
|
||||||
title="Thread author is banned")
|
title="Thread author is banned")
|
||||||
|
|
@ -227,7 +223,7 @@ when defined(js):
|
||||||
|
|
||||||
let isLastThread = i+1 == list.threads.len
|
let isLastThread = i+1 == list.threads.len
|
||||||
let (isLastUnseen, isNew) = getInfo(list.threads, i, currentUser)
|
let (isLastUnseen, isNew) = getInfo(list.threads, i, currentUser)
|
||||||
genThread(i+1, thread, isNew,
|
genThread(thread, isNew,
|
||||||
noBorder=isLastUnseen or isLastThread,
|
noBorder=isLastUnseen or isLastThread,
|
||||||
displayCategory=displayCategory)
|
displayCategory=displayCategory)
|
||||||
if isLastUnseen and (not isLastThread):
|
if isLastUnseen and (not isLastThread):
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ SELECT
|
||||||
post_id,
|
post_id,
|
||||||
post_content,
|
post_content,
|
||||||
cdate,
|
cdate,
|
||||||
person.id,
|
|
||||||
person.name AS author,
|
person.name AS author,
|
||||||
person.email AS email,
|
person.email AS email,
|
||||||
strftime('%s', person.lastOnline) AS lastOnline,
|
strftime('%s', person.lastOnline) AS lastOnline,
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,6 @@ proc initialiseDb(admin: tuple[username, password, email: string],
|
||||||
isLocked boolean not null default 0,
|
isLocked boolean not null default 0,
|
||||||
solution integer,
|
solution integer,
|
||||||
isDeleted boolean not null default 0,
|
isDeleted boolean not null default 0,
|
||||||
isPinned boolean not null default 0,
|
|
||||||
|
|
||||||
foreign key (category) references category(id),
|
foreign key (category) references category(id),
|
||||||
foreign key (solution) references post(id)
|
foreign key (solution) references post(id)
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,7 @@ proc elementIsSome(element: Option[Element]): bool =
|
||||||
proc elementIsNone(element: Option[Element]): bool =
|
proc elementIsNone(element: Option[Element]): bool =
|
||||||
return element.isNone
|
return element.isNone
|
||||||
|
|
||||||
proc waitForElement*(session: Session, selector: string, strategy=CssSelector, timeout=20000, pollTime=50,
|
proc waitForElement*(session: Session, selector: string, strategy=CssSelector, timeout=20000, pollTime=50, waitCondition=elementIsSome): Option[Element]
|
||||||
waitCondition: proc(element: Option[Element]): bool = elementIsSome): Option[Element]
|
|
||||||
|
|
||||||
proc click*(session: Session, element: string, strategy=CssSelector) =
|
proc click*(session: Session, element: string, strategy=CssSelector) =
|
||||||
let el = session.waitForElement(element, strategy)
|
let el = session.waitForElement(element, strategy)
|
||||||
|
|
@ -72,14 +71,14 @@ proc setColor*(session: Session, element, color: string, strategy=CssSelector) =
|
||||||
proc checkIsNone*(session: Session, element: string, strategy=CssSelector) =
|
proc checkIsNone*(session: Session, element: string, strategy=CssSelector) =
|
||||||
discard session.waitForElement(element, strategy, waitCondition=elementIsNone)
|
discard session.waitForElement(element, strategy, waitCondition=elementIsNone)
|
||||||
|
|
||||||
template checkText*(session: Session, element, expectedValue: string) =
|
proc checkText*(session: Session, element, expectedValue: string) =
|
||||||
let el = session.waitForElement(element)
|
let el = session.waitForElement(element)
|
||||||
check el.get().getText() == expectedValue
|
check el.get().getText() == expectedValue
|
||||||
|
|
||||||
proc waitForElement*(
|
proc waitForElement*(
|
||||||
session: Session, selector: string, strategy=CssSelector,
|
session: Session, selector: string, strategy=CssSelector,
|
||||||
timeout=20000, pollTime=50,
|
timeout=20000, pollTime=50,
|
||||||
waitCondition: proc(element: Option[Element]): bool = elementIsSome
|
waitCondition=elementIsSome
|
||||||
): Option[Element] =
|
): Option[Element] =
|
||||||
var waitTime = 0
|
var waitTime = 0
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,4 +40,4 @@ proc test*(session: Session, baseUrl: string) =
|
||||||
register "TEst1", "test1", verify = false
|
register "TEst1", "test1", verify = false
|
||||||
|
|
||||||
ensureExists "#signup-form .has-error"
|
ensureExists "#signup-form .has-error"
|
||||||
navigate baseUrl
|
navigate baseUrl
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import unittest, common
|
import unittest, common
|
||||||
|
|
||||||
import webdriver
|
import webdriver
|
||||||
|
|
||||||
let
|
let
|
||||||
|
|
@ -57,35 +58,10 @@ proc userTests(session: Session, baseUrl: string) =
|
||||||
# Make sure the forum post is gone
|
# Make sure the forum post is gone
|
||||||
checkIsNone "To be deleted", LinkTextSelector
|
checkIsNone "To be deleted", LinkTextSelector
|
||||||
|
|
||||||
test "cannot (un)pin thread":
|
|
||||||
with session:
|
|
||||||
navigate(baseUrl)
|
|
||||||
|
|
||||||
click "#new-thread-btn"
|
|
||||||
|
|
||||||
sendKeys "#thread-title", "Unpinnable"
|
|
||||||
sendKeys "#reply-textarea", "Cannot (un)pin as an user"
|
|
||||||
|
|
||||||
click "#create-thread-btn"
|
|
||||||
|
|
||||||
checkIsNone "#pin-btn"
|
|
||||||
|
|
||||||
test "cannot lock threads":
|
|
||||||
with session:
|
|
||||||
navigate(baseUrl)
|
|
||||||
|
|
||||||
click "#new-thread-btn"
|
|
||||||
|
|
||||||
sendKeys "#thread-title", "Locking"
|
|
||||||
sendkeys "#reply-textarea", "Cannot lock as an user"
|
|
||||||
|
|
||||||
click "#create-thread-btn"
|
|
||||||
|
|
||||||
checkIsNone "#lock-btn"
|
|
||||||
|
|
||||||
session.logout()
|
session.logout()
|
||||||
|
|
||||||
proc anonymousTests(session: Session, baseUrl: string) =
|
proc anonymousTests(session: Session, baseUrl: string) =
|
||||||
|
|
||||||
suite "anonymous user tests":
|
suite "anonymous user tests":
|
||||||
with session:
|
with session:
|
||||||
navigate baseUrl
|
navigate baseUrl
|
||||||
|
|
@ -185,70 +161,6 @@ proc adminTests(session: Session, baseUrl: string) =
|
||||||
# Make sure the forum post is gone
|
# Make sure the forum post is gone
|
||||||
checkIsNone adminTitleStr, LinkTextSelector
|
checkIsNone adminTitleStr, LinkTextSelector
|
||||||
|
|
||||||
test "can pin a thread":
|
|
||||||
with session:
|
|
||||||
click "#new-thread-btn"
|
|
||||||
sendKeys "#thread-title", "Pinned post"
|
|
||||||
sendKeys "#reply-textarea", "A pinned post"
|
|
||||||
click "#create-thread-btn"
|
|
||||||
|
|
||||||
navigate(baseUrl)
|
|
||||||
click "#new-thread-btn"
|
|
||||||
sendKeys "#thread-title", "Normal post"
|
|
||||||
sendKeys "#reply-textarea", "A normal post"
|
|
||||||
click "#create-thread-btn"
|
|
||||||
|
|
||||||
navigate(baseUrl)
|
|
||||||
click "Pinned post", LinkTextSelector
|
|
||||||
click "#pin-btn"
|
|
||||||
checkText "#pin-btn", "Unpin Thread"
|
|
||||||
|
|
||||||
navigate(baseUrl)
|
|
||||||
|
|
||||||
# Make sure pin exists
|
|
||||||
ensureExists "#threads-list .thread-1 .thread-title i"
|
|
||||||
|
|
||||||
checkText "#threads-list .thread-1 .thread-title a", "Pinned post"
|
|
||||||
checkText "#threads-list .thread-2 .thread-title a", "Normal post"
|
|
||||||
|
|
||||||
test "can unpin a thread":
|
|
||||||
with session:
|
|
||||||
click "Pinned post", LinkTextSelector
|
|
||||||
click "#pin-btn"
|
|
||||||
checkText "#pin-btn", "Pin Thread"
|
|
||||||
|
|
||||||
navigate(baseUrl)
|
|
||||||
|
|
||||||
checkIsNone "#threads-list .thread-2 .thread-title i"
|
|
||||||
|
|
||||||
checkText "#threads-list .thread-1 .thread-title a", "Normal post"
|
|
||||||
checkText "#threads-list .thread-2 .thread-title a", "Pinned post"
|
|
||||||
|
|
||||||
test "can lock a thread":
|
|
||||||
with session:
|
|
||||||
click "Locking", LinkTextSelector
|
|
||||||
click "#lock-btn"
|
|
||||||
|
|
||||||
ensureExists "#thread-title i.fas.fa-lock.fa-xs"
|
|
||||||
|
|
||||||
test "locked thread appears on frontpage":
|
|
||||||
with session:
|
|
||||||
click "#new-thread-btn"
|
|
||||||
sendKeys "#thread-title", "A new locked thread"
|
|
||||||
sendKeys "#reply-textarea", "This thread should appear locked on the frontpage"
|
|
||||||
click "#create-thread-btn"
|
|
||||||
click "#lock-btn"
|
|
||||||
|
|
||||||
navigate(baseUrl)
|
|
||||||
ensureExists "#threads-list .thread-1 .thread-title i.fas.fa-lock.fa-xs"
|
|
||||||
|
|
||||||
test "can unlock a thread":
|
|
||||||
with session:
|
|
||||||
click "Locking", LinkTextSelector
|
|
||||||
click "#lock-btn"
|
|
||||||
|
|
||||||
checkIsNone "#thread-title i.fas.fa-lock.fa-xs"
|
|
||||||
|
|
||||||
session.logout()
|
session.logout()
|
||||||
|
|
||||||
proc test*(session: Session, baseUrl: string) =
|
proc test*(session: Session, baseUrl: string) =
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue