mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge branch 'master' into backend_setup
This commit is contained in:
commit
b6d51145df
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@ -261,7 +261,7 @@ jobs:
|
||||
report_name: Coverage Frontend
|
||||
type: lcov
|
||||
result_path: ./coverage/lcov.info
|
||||
min_coverage: 32
|
||||
min_coverage: 46
|
||||
token: ${{ github.token }}
|
||||
|
||||
##############################################################################
|
||||
|
||||
@ -25,7 +25,7 @@ class AppRequestsController extends AppController
|
||||
$this->loadComponent('GradidoNumber');
|
||||
//$this->loadComponent('JsonRpcRequestClient');
|
||||
//$this->Auth->allow(['add', 'edit']);
|
||||
$this->Auth->allow(['index', 'sendCoins', 'createCoins', 'getBalance', 'listTransactions']);
|
||||
$this->Auth->allow(['index', 'sendCoins', 'createCoins', 'getBalance', 'listTransactions', 'getDecayStartBlock']);
|
||||
}
|
||||
|
||||
|
||||
@ -333,16 +333,37 @@ class AppRequestsController extends AppController
|
||||
$this->addAdminError('StateBalancesController', 'overview', $gdtEntries, $user['id'] ? $user['id'] : 0);
|
||||
}
|
||||
|
||||
$limit = $count;
|
||||
$offset = 0;
|
||||
$skip_first_transaction = false;
|
||||
if($page == 1) {
|
||||
$limit--;
|
||||
} else {
|
||||
$offset = (( $page - 1 ) * $count) - 1;
|
||||
}
|
||||
if($offset && $orderDirection == 'ASC') {
|
||||
// move cursor one step backwards to able to load one transaction previous last which will be shown for decay calculation
|
||||
$offset--;
|
||||
$limit++;
|
||||
$skip_first_transaction = true;
|
||||
} else if($orderDirection == 'DESC') {
|
||||
$limit++;
|
||||
$skip_first_transaction = true;
|
||||
}
|
||||
|
||||
$stateUserTransactionsQuery = $stateUserTransactionsTable
|
||||
->find()
|
||||
->where(['state_user_id' => $user['id']])
|
||||
->order(['balance_date' => $orderDirection])
|
||||
->contain([])
|
||||
->limit($count)
|
||||
->page($page)
|
||||
->limit($limit)
|
||||
//->page($page)
|
||||
->offset($offset)
|
||||
;
|
||||
$decay = true;
|
||||
if($page > 1) {
|
||||
$decay = false;
|
||||
}
|
||||
$transactions = [];
|
||||
$transactions_from_db = $stateUserTransactionsQuery->toArray();
|
||||
|
||||
@ -351,7 +372,7 @@ class AppRequestsController extends AppController
|
||||
$transactions_from_db = array_reverse($transactions_from_db);
|
||||
}
|
||||
|
||||
$transactions = $transactionsTable->listTransactionsHumanReadable($transactions_from_db, $user, $decay);
|
||||
$transactions = $transactionsTable->listTransactionsHumanReadable($transactions_from_db, $user, $decay, $skip_first_transaction);
|
||||
|
||||
if($orderDirection == 'DESC') {
|
||||
$transactions = array_reverse($transactions);
|
||||
@ -382,6 +403,16 @@ class AppRequestsController extends AppController
|
||||
$this->set('body', $body);
|
||||
}
|
||||
|
||||
public function getDecayStartBlock()
|
||||
{
|
||||
$transactionsTable = TableRegistry::getTableLocator()->get('Transactions');
|
||||
$decayStartBlock = $transactionsTable->find()->where(['transaction_type_id' => 9]);
|
||||
if(!$decayStartBlock->count()) {
|
||||
return $this->returnJson(['state' => 'error', 'msg' => 'not found']);
|
||||
}
|
||||
return $this->returnJson(['state' => 'success', 'decay_start' => $decayStartBlock->first()->received]);
|
||||
}
|
||||
|
||||
private function acquireAccessToken($session_id)
|
||||
{
|
||||
|
||||
|
||||
@ -98,7 +98,12 @@ class StateBalancesTable extends AppTable
|
||||
// if start date for decay is after enddate, we also just return input
|
||||
if($decayStartDate === null || $decayStartDate >= $endDate) {
|
||||
if($withInterval) {
|
||||
return ['balance' => $startBalance, 'interval' => new \DateInterval('PT0S')];
|
||||
return [
|
||||
'balance' => $startBalance,
|
||||
'interval' => new \DateInterval('PT0S'),
|
||||
'start_date' => $startDate->getTimestamp(),
|
||||
'end_date' => $startDate->getTimestamp()
|
||||
];
|
||||
} else {
|
||||
return $startBalance;
|
||||
}
|
||||
@ -118,7 +123,12 @@ class StateBalancesTable extends AppTable
|
||||
}
|
||||
$decay = $state_balance->partDecay($endDate);
|
||||
if($withInterval) {
|
||||
return ['balance' => $decay, 'interval' => $interval];
|
||||
return [
|
||||
'balance' => $decay,
|
||||
'interval' => $interval,
|
||||
'start_date' => $state_balance->record_date->getTimestamp(),
|
||||
'end_date' => $endDate->getTimestamp()
|
||||
];
|
||||
} else {
|
||||
return $decay;
|
||||
}
|
||||
|
||||
@ -133,11 +133,12 @@ class TransactionsTable extends Table
|
||||
}
|
||||
|
||||
|
||||
public function listTransactionsHumanReadable($stateUserTransactions, array $user, $decay = true)
|
||||
public function listTransactionsHumanReadable($stateUserTransactions, array $user, $decay = true, $skip_first_transaction = false)
|
||||
{
|
||||
|
||||
$stateUsersTable = TableRegistry::getTableLocator()->get('StateUsers');
|
||||
$stateBalancesTable = TableRegistry::getTableLocator()->get('StateBalances');
|
||||
$transactionsTable = TableRegistry::getTableLocator()->get('Transactions');
|
||||
|
||||
$transaction_ids = [];
|
||||
$involved_user_ids = [];
|
||||
@ -162,11 +163,15 @@ class TransactionsTable extends Table
|
||||
|
||||
$state_balance = $stateBalancesTable->newEntity();
|
||||
$final_transactions = [];
|
||||
$decay_start_transaction = $transactionsTable->find()->where(['transaction_type_id' => 9]);
|
||||
$decay_start_transaction_id = 0;
|
||||
if($decay_start_transaction->count()) {
|
||||
$decay_start_transaction_id = $decay_start_transaction->first()->id;
|
||||
}
|
||||
$decay_start_time = $stateBalancesTable->getDecayStartDateCached()->getTimestamp();
|
||||
|
||||
foreach($stateUserTransactions as $i => $su_transaction)
|
||||
{
|
||||
|
||||
|
||||
// sender or receiver when user has sended money
|
||||
// group name if creation
|
||||
// type: gesendet / empfangen / geschöpft
|
||||
@ -185,19 +190,34 @@ class TransactionsTable extends Table
|
||||
if($i > 0 ) {
|
||||
$prev = $stateUserTransactions[$i-1];
|
||||
}
|
||||
if($prev && $decay == true)
|
||||
if($prev)
|
||||
{
|
||||
if($prev->balance > 0) {
|
||||
if($prev->balance > 0)
|
||||
{
|
||||
$current = $su_transaction;
|
||||
$calculated_decay = $stateBalancesTable->calculateDecay($prev->balance, $prev->balance_date, $current->balance_date, true);
|
||||
$balance = floatval($prev->balance - $calculated_decay['balance']);
|
||||
|
||||
if($balance > 100)
|
||||
if($balance)
|
||||
{
|
||||
$final_transactions['decay'] = [
|
||||
$final_transaction['decay'] = [
|
||||
'balance' => $balance,
|
||||
'decay_duration' => $calculated_decay['interval']->format('%a days, %H hours, %I minutes, %S seconds')
|
||||
];
|
||||
'decay_duration' => $calculated_decay['interval']->format('%a days, %H hours, %I minutes, %S seconds'),
|
||||
'decay_start' => $calculated_decay['start_date'],
|
||||
'decay_end' => $calculated_decay['end_date']
|
||||
];
|
||||
if($prev->transaction_id < $decay_start_transaction_id &&
|
||||
$current->transaction_id > $decay_start_transaction_id) {
|
||||
$final_transaction['decay']['decay_start_block'] = $decay_start_time;
|
||||
}
|
||||
// hint: use transaction id
|
||||
/*if($calculated_decay['start_date'] < $decay_start_time && $calculated_decay['end_date'] > $decay_start_time) {
|
||||
$final_transaction['decay']['decay_start_block'] = $decay_start_time;
|
||||
} else {
|
||||
echo "start block: " . $decay_start_time . "<br>";
|
||||
echo "start date: " . $calculated_decay['start_date'] . "<br>";
|
||||
echo "end date: " . $calculated_decay['end_date']. "<hr>";
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -247,24 +267,28 @@ class TransactionsTable extends Table
|
||||
$final_transaction['name'] = $otherUser->first_name . ' ' . $otherUser->last_name;
|
||||
$final_transaction['email'] = $otherUser->email;
|
||||
}
|
||||
|
||||
$final_transactions[] = $final_transaction;
|
||||
if($i > 0 || !$skip_first_transaction) {
|
||||
$final_transactions[] = $final_transaction;
|
||||
}
|
||||
|
||||
if($i == $stateUserTransactionsCount-1 && $decay == true) {
|
||||
if($i == $stateUserTransactionsCount-1 && $decay) {
|
||||
$now = new FrozenTime();
|
||||
$calculated_decay = $stateBalancesTable->calculateDecay(
|
||||
$su_transaction->balance,
|
||||
$su_transaction->balance_date, new FrozenTime(), true);
|
||||
$su_transaction->balance_date, $now, true);
|
||||
$decay_start_date = $stateBalancesTable->getDecayStartDateCached();
|
||||
$duration = $su_transaction->balance_date->timeAgoInWords();
|
||||
if($decay_start_date > $su_transaction->balance_date) {
|
||||
$duration = $decay_start_date->timeAgoInWords();
|
||||
}
|
||||
$balance = floatval($su_transaction->balance - $calculated_decay['balance']);
|
||||
if($balance > 100) {
|
||||
if($balance) {
|
||||
$final_transactions[] = [
|
||||
'type' => 'decay',
|
||||
'balance' => $balance,
|
||||
'decay_duration' => $duration,
|
||||
'decay_start' => $calculated_decay['start_date'],
|
||||
'decay_end' => $calculated_decay['end_date'],
|
||||
'memo' => ''
|
||||
];
|
||||
}
|
||||
|
||||
@ -12,15 +12,19 @@ $body['gdtSum'] = $this->element('centToFloat', ['cent' => $body['gdtSum'], 'pre
|
||||
|
||||
foreach($body['transactions'] as $i => $transaction) {
|
||||
$useCeil = false;
|
||||
if($transaction['type'] == 'decay') {
|
||||
$useCeil = true;
|
||||
}
|
||||
$body['transactions'][$i]['balance'] = $this->element('centToFloat', ['cent' => $transaction['balance'], 'precision' => 4, 'useCeil' => $useCeil]);
|
||||
if(isset($transaction['creation_amount'])) {
|
||||
$body['transactions'][$i]['creation_amount'] = $this->element('centToFloat', ['cent' => $transaction['creation_amount'], 'precision' => 4]);
|
||||
}
|
||||
if(isset($transaction['decay'])) {
|
||||
$body['transactions'][$i]['decay']['balance'] = $this->element('centToFloat', ['cent' => $transaction['decay']['balance'], 'precision' => 4]);
|
||||
if(!isset($transaction['type'])) {
|
||||
$body = ['state' => 'error', 'msg' => 'transaction without type found', 'details' => $transaction];
|
||||
} else {
|
||||
if($transaction['type'] == 'decay') {
|
||||
$useCeil = true;
|
||||
}
|
||||
$body['transactions'][$i]['balance'] = $this->element('centToFloat', ['cent' => $transaction['balance'], 'precision' => 4, 'useCeil' => $useCeil]);
|
||||
if(isset($transaction['creation_amount'])) {
|
||||
$body['transactions'][$i]['creation_amount'] = $this->element('centToFloat', ['cent' => $transaction['creation_amount'], 'precision' => 4]);
|
||||
}
|
||||
if(isset($transaction['decay'])) {
|
||||
$body['transactions'][$i]['decay']['balance'] = $this->element('centToFloat', ['cent' => $transaction['decay']['balance'], 'precision' => 4]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -46,12 +46,12 @@ class StateUserTransactionsFixture extends BaseTestFixture
|
||||
[8, 4, 4, 2, 0, '2021-04-14 00:00:00'],
|
||||
[23, 1, 5, 2, 0, '2021-04-14 09:01:07'],
|
||||
[24, 4, 5, 2, 0, '2021-04-14 09:01:07'],
|
||||
[25, 4, 6, 2, 0, '2021-04-14 09:02:28'],
|
||||
[26, 1, 6, 2, 0, '2021-04-14 09:02:28'],
|
||||
[27, 4, 7, 2, 0, '2021-04-14 09:28:46'],
|
||||
[28, 1, 7, 2, 0, '2021-04-14 09:28:46'],
|
||||
[29, 4, 8, 2, 0, '2021-04-14 09:31:28'],
|
||||
[30, 1, 8, 2, 0, '2021-04-14 09:31:28']
|
||||
[25, 4, 7, 2, 0, '2021-04-14 09:02:28'],
|
||||
[26, 1, 7, 2, 0, '2021-04-14 09:02:28'],
|
||||
[27, 4, 8, 2, 0, '2021-04-14 09:28:46'],
|
||||
[28, 1, 8, 2, 0, '2021-04-14 09:28:46'],
|
||||
[29, 4, 9, 2, 0, '2021-04-14 09:31:28'],
|
||||
[30, 1, 9, 2, 0, '2021-04-14 09:31:28']
|
||||
];
|
||||
$this->records = $this->sqlEntrysToRecords($sql, $this->fields);
|
||||
parent::init();
|
||||
|
||||
@ -43,9 +43,9 @@ class TransactionSendCoinsFixture extends BaseTestFixture
|
||||
[2, 3, '0000000000000000000000000000000000000000000000000000000000000000', 1, 'e3369de3623ce8446d0424c4013e7a1d71a2671ae3d7bf1e798ebf0665d145f2', 4, 1000000, 6254699],
|
||||
[3, 4, '0000000000000000000000000000000000000000000000000000000000000000', 1, 'e3369de3623ce8446d0424c4013e7a1d71a2671ae3d7bf1e798ebf0665d145f2', 4, 100000, 7027197],
|
||||
[11, 5, '0000000000000000000000000000000000000000000000000000000000000000', 1, 'e3369de3623ce8446d0424c4013e7a1d71a2671ae3d7bf1e798ebf0665d145f2', 4, 100000, 6922113],
|
||||
[12, 6, '0000000000000000000000000000000000000000000000000000000000000000', 4, 'f7f4a49a4ac10379f8b9ddcb731c4d9ec495e6edd16075f52672cd25e3179f0f', 1, 100000, 9212951],
|
||||
[13, 7, '0000000000000000000000000000000000000000000000000000000000000000', 4, 'f7f4a49a4ac10379f8b9ddcb731c4d9ec495e6edd16075f52672cd25e3179f0f', 1, 100000, 9112627],
|
||||
[14, 8, '0000000000000000000000000000000000000000000000000000000000000000', 4, 'f7f4a49a4ac10379f8b9ddcb731c4d9ec495e6edd16075f52672cd25e3179f0f', 1, 100000, 8912594]
|
||||
[12, 7, '0000000000000000000000000000000000000000000000000000000000000000', 4, 'f7f4a49a4ac10379f8b9ddcb731c4d9ec495e6edd16075f52672cd25e3179f0f', 1, 100000, 9212951],
|
||||
[13, 8, '0000000000000000000000000000000000000000000000000000000000000000', 4, 'f7f4a49a4ac10379f8b9ddcb731c4d9ec495e6edd16075f52672cd25e3179f0f', 1, 100000, 9112627],
|
||||
[14, 9, '0000000000000000000000000000000000000000000000000000000000000000', 4, 'f7f4a49a4ac10379f8b9ddcb731c4d9ec495e6edd16075f52672cd25e3179f0f', 1, 100000, 8912594]
|
||||
];
|
||||
|
||||
$this->records = $this->sqlEntrysToRecords($sql, $this->fields);
|
||||
|
||||
@ -41,9 +41,9 @@ class TransactionSignaturesFixture extends BaseTestFixture
|
||||
[3, 3, 'c70f124feaaea02194d22a5f597963ed3e430343122a0952877854766fe37a709f92b39510de2aae494ef11abe743cd59f08f971b1e0e36f4c333990453d8b0d', 'f7f4a49a4ac10379f8b9ddcb731c4d9ec495e6edd16075f52672cd25e3179f0f'],
|
||||
[4, 4, 'a65b39e51ab6191c51d5629bbcefd30f85f801efbb14e1c635c519e97abe217a248820fa1fc6aef56227c9d888c1919bc92471d5d7ae3522c9c50fba9f0d8402', 'f7f4a49a4ac10379f8b9ddcb731c4d9ec495e6edd16075f52672cd25e3179f0f'],
|
||||
[5, 5, 'a65b39e51ab6191c51d5629bbcefd30f85f801efbb14e1c635c519e97abe217a248820fa1fc6aef56227c9d888c1919bc92471d5d7ae3522c9c50fba9f0d8402', 'f7f4a49a4ac10379f8b9ddcb731c4d9ec495e6edd16075f52672cd25e3179f0f'],
|
||||
[6, 6, 'c233726674bff9bfb8ccb98bf358c6bc701825d971ece915d3c3a3de98886d1d13ee2f773cd9fc4ccbe543ac17be0d780ebead23a0dbf4ec814f7bae2efb9c0e', 'e3369de3623ce8446d0424c4013e7a1d71a2671ae3d7bf1e798ebf0665d145f2'],
|
||||
[7, 7, '83ab780535883ec53ee76d0f68db0e1596418c9e100c806a4d4655d4dedf589d54a6319a2795dabab301e212b52f0dafb2725b7583447f19e47cb417d188a107', 'e3369de3623ce8446d0424c4013e7a1d71a2671ae3d7bf1e798ebf0665d145f2'],
|
||||
[8, 8, '83ab780535883ec53ee76d0f68db0e1596418c9e100c806a4d4655d4dedf589d54a6319a2795dabab301e212b52f0dafb2725b7583447f19e47cb417d188a107', 'e3369de3623ce8446d0424c4013e7a1d71a2671ae3d7bf1e798ebf0665d145f2']
|
||||
[6, 7, 'c233726674bff9bfb8ccb98bf358c6bc701825d971ece915d3c3a3de98886d1d13ee2f773cd9fc4ccbe543ac17be0d780ebead23a0dbf4ec814f7bae2efb9c0e', 'e3369de3623ce8446d0424c4013e7a1d71a2671ae3d7bf1e798ebf0665d145f2'],
|
||||
[7, 8, '83ab780535883ec53ee76d0f68db0e1596418c9e100c806a4d4655d4dedf589d54a6319a2795dabab301e212b52f0dafb2725b7583447f19e47cb417d188a107', 'e3369de3623ce8446d0424c4013e7a1d71a2671ae3d7bf1e798ebf0665d145f2'],
|
||||
[8, 9, '83ab780535883ec53ee76d0f68db0e1596418c9e100c806a4d4655d4dedf589d54a6319a2795dabab301e212b52f0dafb2725b7583447f19e47cb417d188a107', 'e3369de3623ce8446d0424c4013e7a1d71a2671ae3d7bf1e798ebf0665d145f2']
|
||||
];
|
||||
$this->records = $this->sqlEntrysToRecords($sql, $this->fields);
|
||||
parent::init();
|
||||
|
||||
@ -44,9 +44,10 @@ class TransactionsFixture extends BaseTestFixture
|
||||
[3, NULL, 2, '4e2235f208edaf5cbb285955732022a625cf1e100eb629c56896d2fbfb8b34e800000000000000000000000000000000', 'test', '2021-04-12 00:00:00', 1],
|
||||
[4, NULL, 2, 'fc6e69696beb7c56ad7c511fc3999f954411427bec810184b70c092911deae1900000000000000000000000000000000', 'test time', '2021-04-14 00:00:00', 1],
|
||||
[5, NULL, 2, 'a7149ebc0d6cd8c061906dafe05e13689b51642a41100d0ec7bb6cd2dcafdc1800000000000000000000000000000000', 'test time', '2021-04-14 09:01:07', 1],
|
||||
[6, NULL, 2, '2e3c3ab3e42c06f2ecb12f61c970712467d8ad9ddfa16fa58dd76492e5924b7d00000000000000000000000000000000', 'test time 3', '2021-04-14 09:02:28', 1],
|
||||
[7, NULL, 2, 'c2c6354d77ff371daeee25fce9c947748b53d3d6b8398a92bd681923cfd2057100000000000000000000000000000000', 'test login crash', '2021-04-14 09:28:46', 1],
|
||||
[8, NULL, 2, '5a8cbf1aaac06b00b2951ff39983cb2ca9a1e6710d72c8e5067278dc679a823100000000000000000000000000000000', 'test login crash', '2021-04-14 09:31:28', 1]
|
||||
[6, NULL, 9, '', '', '2021-04-14 09:02:00', 1],
|
||||
[7, NULL, 2, '2e3c3ab3e42c06f2ecb12f61c970712467d8ad9ddfa16fa58dd76492e5924b7d00000000000000000000000000000000', 'test time 3', '2021-04-14 09:02:28', 1],
|
||||
[8, NULL, 2, 'c2c6354d77ff371daeee25fce9c947748b53d3d6b8398a92bd681923cfd2057100000000000000000000000000000000', 'test login crash', '2021-04-14 09:28:46', 1],
|
||||
[9, NULL, 2, '5a8cbf1aaac06b00b2951ff39983cb2ca9a1e6710d72c8e5067278dc679a823100000000000000000000000000000000', 'test login crash', '2021-04-14 09:31:28', 1]
|
||||
];
|
||||
$this->records = $this->sqlEntrysToRecords($sql, $this->fields);
|
||||
parent::init();
|
||||
|
||||
@ -57,8 +57,8 @@ class AppRequestControllerTest extends TestCase
|
||||
|
||||
$response = $this->getAndParseWithoutCompare('/api/get-balance/' . $session_id);
|
||||
$this->assertEquals('success', $response->state);
|
||||
$this->assertEquals(9100000, $response->balance);
|
||||
$this->assertLessThan(9100000, $response->decay);
|
||||
$this->assertEquals(9099652, $response->balance);
|
||||
$this->assertLessThan(9099652, $response->decay);
|
||||
|
||||
}
|
||||
|
||||
@ -94,8 +94,8 @@ class AppRequestControllerTest extends TestCase
|
||||
|
||||
$response = $this->getAndParseWithoutCompare('/api/get-balance/' . $session_id);
|
||||
$this->assertEquals('success', $response->state);
|
||||
$this->assertEquals(10900000, $response->balance);
|
||||
$this->assertLessThan(10900000, $response->decay);
|
||||
$this->assertEquals(10899568, $response->balance);
|
||||
$this->assertLessThan(10899568, $response->decay);
|
||||
}
|
||||
|
||||
public function testGetBalanceInvalidSession()
|
||||
@ -202,19 +202,16 @@ class AppRequestControllerTest extends TestCase
|
||||
"email": "test3.yahoo.com"
|
||||
},
|
||||
{
|
||||
"transaction_id": 6,
|
||||
"transaction_id": 7,
|
||||
"date": "2021-04-14T09:02:28+00:00",
|
||||
"memo": "test time 3",
|
||||
"balance": 100000,
|
||||
"type": "receive",
|
||||
"pubkey": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"name": "Samuel Schmied",
|
||||
"email": "test3.yahoo.com"
|
||||
},
|
||||
{
|
||||
"transaction_id": 7,
|
||||
"date": "2021-04-14T09:28:46+00:00",
|
||||
"memo": "test login crash",
|
||||
"decay": {
|
||||
"balance": 6,
|
||||
"decay_duration": "0 days, 00 hours, 00 minutes, 28 seconds",
|
||||
"decay_start": 1618390920,
|
||||
"decay_end": 1618390948,
|
||||
"decay_start_block": 1618390920
|
||||
},
|
||||
"balance": 100000,
|
||||
"type": "receive",
|
||||
"pubkey": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
@ -223,22 +220,52 @@ class AppRequestControllerTest extends TestCase
|
||||
},
|
||||
{
|
||||
"transaction_id": 8,
|
||||
"date": "2021-04-14T09:31:28+00:00",
|
||||
"date": "2021-04-14T09:28:46+00:00",
|
||||
"memo": "test login crash",
|
||||
"decay": {
|
||||
"balance": 309,
|
||||
"decay_duration": "0 days, 00 hours, 26 minutes, 18 seconds",
|
||||
"decay_start": 1618390948,
|
||||
"decay_end": 1618392526
|
||||
},
|
||||
"balance": 100000,
|
||||
"type": "receive",
|
||||
"pubkey": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"name": "Samuel Schmied",
|
||||
"email": "test3.yahoo.com"
|
||||
},
|
||||
{
|
||||
"transaction_id": 9,
|
||||
"date": "2021-04-14T09:31:28+00:00",
|
||||
"memo": "test login crash",
|
||||
"decay": {
|
||||
"balance": 33,
|
||||
"decay_duration": "0 days, 00 hours, 02 minutes, 42 seconds",
|
||||
"decay_start": 1618392526,
|
||||
"decay_end": 1618392688
|
||||
},
|
||||
"balance": 100000,
|
||||
"type": "receive",
|
||||
"pubkey": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"name": "Samuel Schmied",
|
||||
"email": "test3.yahoo.com"
|
||||
},
|
||||
{
|
||||
"type": "decay",
|
||||
"balance": 1345726,
|
||||
"decay_duration": "on 14.04.21",
|
||||
"decay_start": 1618392688,
|
||||
"decay_end": 1625673853,
|
||||
"memo": ""
|
||||
}
|
||||
],
|
||||
"transactionExecutingCount": 0,
|
||||
"count": 7,
|
||||
"gdtSum": 180000,
|
||||
"timeUsed": 0.7237420082092285,
|
||||
"decay_date": "2021-06-22T08:54:43+00:00",
|
||||
"balance": 9100000,
|
||||
"decay": 9100000
|
||||
"timeUsed": 0.44154810905456545,
|
||||
"decay_date": "2021-07-07T16:04:13+00:00",
|
||||
"balance": 9099652,
|
||||
"decay": 7753926
|
||||
}';
|
||||
$this->getAndParse('/api/list-transactions/', json_decode($expectedResult, true));
|
||||
}
|
||||
@ -276,6 +303,19 @@ class AppRequestControllerTest extends TestCase
|
||||
$expected[$field] = $json->$field;
|
||||
}
|
||||
}
|
||||
// decay balance variy always
|
||||
if(isset($expected['transactions'])) {
|
||||
$dynamic_transaction_fields = ['decay_duration', 'balance', 'decay_end'];
|
||||
foreach($expected['transactions'] as $i => $transaction) {
|
||||
if(isset($transaction['type']) && $transaction['type'] == 'decay') {
|
||||
foreach($dynamic_transaction_fields as $field) {
|
||||
if(isset($transaction[$field])) {
|
||||
$expected['transactions'][$i][$field] = $json->transactions[$i][$field];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$expected = json_encode($expected);
|
||||
}
|
||||
|
||||
|
||||
@ -63,6 +63,8 @@ Assuming: session is valid
|
||||
"type": "decay",
|
||||
"balance": "14.74",
|
||||
"decay_duration": "4 days, 2 hours ago",
|
||||
"decay_start": 1618390948,
|
||||
"decay_end": 1618392526,
|
||||
"memo": ""
|
||||
},
|
||||
{
|
||||
@ -73,7 +75,13 @@ Assuming: session is valid
|
||||
"date": "2021-02-19T13:25:36+00:00",
|
||||
"balance": 192.0,
|
||||
"memo": "a piece of cake :)",
|
||||
"pubkey": "038a6f93270dc57b91d76bf110ad3863fcb7d1b08e7692e793fcdb4467e5b6a7"
|
||||
"pubkey": "038a6f93270dc57b91d76bf110ad3863fcb7d1b08e7692e793fcdb4467e5b6a7",
|
||||
"decay": {
|
||||
"balance": 309,
|
||||
"decay_duration": "0 days, 00 hours, 26 minutes, 18 seconds",
|
||||
"decay_start": 1618390948,
|
||||
"decay_end": 1618392526
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "Gradido Akademie",
|
||||
|
||||
@ -59,7 +59,6 @@
|
||||
"sweetalert2": "^9.5.4",
|
||||
"vee-validate": "^3.4.5",
|
||||
"vue": "^2.6.11",
|
||||
"vue-bootstrap-toasts": "^1.0.7",
|
||||
"vue-bootstrap-typeahead": "^0.2.6",
|
||||
"vue-chartjs": "^3.5.0",
|
||||
"vue-cli-plugin-i18n": "^1.0.1",
|
||||
@ -75,6 +74,7 @@
|
||||
"vue-qrcode": "^0.3.5",
|
||||
"vue-qrcode-reader": "^2.3.16",
|
||||
"vue-router": "^3.0.6",
|
||||
"vue-toasted": "^1.1.28",
|
||||
"vue2-transitions": "^0.2.3",
|
||||
"vuex": "^3.6.0",
|
||||
"vuex-persistedstate": "^4.0.0-beta.3"
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
<div class="">
|
||||
<particles-bg type="custom" :config="config" :bg="true" />
|
||||
<component :is="$route.meta.requiresAuth ? 'DashboardLayout' : 'AuthLayoutGDD'" />
|
||||
<Toasts></Toasts>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -93,7 +93,7 @@ const loginAPI = {
|
||||
update: {
|
||||
'User.first_name': data.firstName,
|
||||
'User.last_name': data.lastName,
|
||||
'User.description': data.description,
|
||||
// 'User.description': data.description,
|
||||
},
|
||||
}
|
||||
return apiPost(CONFIG.LOGIN_API_URL + 'updateUserInfos', payload)
|
||||
|
||||
85
frontend/src/components/DecayInformation.vue
Normal file
85
frontend/src/components/DecayInformation.vue
Normal file
@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<div>
|
||||
<span v-if="decaytyp === 'short'">
|
||||
<small>{{ decay ? ' ' + decay.balance + ' ' + decayStartBlockTextShort : '' }}</small>
|
||||
</span>
|
||||
|
||||
<div v-if="decaytyp === 'new'">
|
||||
<b-list-group style="border: 0px">
|
||||
<b-list-group-item style="border: 0px; background-color: #f1f1f1">
|
||||
<div class="d-flex">
|
||||
<div style="width: 40%" class="text-right pr-3 mr-2">
|
||||
{{ $t('decay.calculation_decay') }}
|
||||
<b-icon icon="droplet-half" height="12" class="mb-2" />
|
||||
</div>
|
||||
<div style="width: 60%"></div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex">
|
||||
<div style="width: 40%" class="text-right pr-3 mr-2">
|
||||
{{ $t('decay.last_transaction') }}
|
||||
</div>
|
||||
<div style="width: 60%">
|
||||
<div v-if="decay.decay_start_block > 0">
|
||||
<div class="display-4">{{ $t('decay.Starting_block_decay') }}</div>
|
||||
<div>
|
||||
{{ $t('decay.decay_introduced') }} :
|
||||
{{ $d($moment.unix(decay.decay_start), 'long') }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ $d($moment.unix(decay.decay_start), 'long') }} Uhr</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex">
|
||||
<div style="width: 40%" class="text-right pr-3 mr-2">
|
||||
{{ $t('decay.past_time') }}
|
||||
</div>
|
||||
<div style="width: 60%">
|
||||
<div v-if="decay.decay_start_block > 0">{{ $t('decay.since_introduction') }}</div>
|
||||
<span v-if="duration">
|
||||
<b v-if="duration.years > 0">{{ duration.years }} {{ $t('decay.year') }},</b>
|
||||
<b v-if="duration.months > 0">{{ duration.months }} {{ $t('decay.months') }},</b>
|
||||
<b v-if="duration.days > 0">{{ duration.days }} {{ $t('decay.days') }},</b>
|
||||
<b v-if="duration.hours > 0">{{ duration.hours }} {{ $t('decay.hours') }},</b>
|
||||
<b v-if="duration.minutes > 0">{{ duration.minutes }} {{ $t('decay.minutes') }},</b>
|
||||
<b v-if="duration.seconds > 0">{{ duration.seconds }} {{ $t('decay.seconds') }}</b>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</b-list-group-item>
|
||||
</b-list-group>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'DecayInformation',
|
||||
props: {
|
||||
decay: {
|
||||
balance: '',
|
||||
decay_duration: '',
|
||||
decay_start: 0,
|
||||
decay_end: 0,
|
||||
decay_start_block: 0,
|
||||
},
|
||||
decaytyp: { type: String, default: '' },
|
||||
},
|
||||
computed: {
|
||||
decayStartBlockTextShort() {
|
||||
return this.decay.decay_start_block
|
||||
? ' - Startblock Decay am: ' + this.$d(this.$moment.unix(this.decay.decay_start_block))
|
||||
: ''
|
||||
},
|
||||
duration() {
|
||||
return this.$moment.duration(
|
||||
this.$moment
|
||||
.unix(new Date(this.decay.decay_end))
|
||||
.diff(this.$moment.unix(new Date(this.decay.decay_start))),
|
||||
)._data
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
71
frontend/src/components/Inputs/InputEmail.spec.js
Normal file
71
frontend/src/components/Inputs/InputEmail.spec.js
Normal file
@ -0,0 +1,71 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
|
||||
import InputEmail from './InputEmail'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
describe('InputEmail', () => {
|
||||
let wrapper
|
||||
|
||||
const propsData = {
|
||||
name: 'input-field-name',
|
||||
label: 'input-field-label',
|
||||
placeholder: 'input-field-placeholder',
|
||||
value: '',
|
||||
}
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(InputEmail, { localVue, propsData })
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('has an input field', () => {
|
||||
expect(wrapper.find('input').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
describe('properties', () => {
|
||||
it('has the name "input-field-name"', () => {
|
||||
expect(wrapper.find('input').attributes('name')).toEqual('input-field-name')
|
||||
})
|
||||
|
||||
it('has the id "input-field-name-input-field"', () => {
|
||||
expect(wrapper.find('input').attributes('id')).toEqual('input-field-name-input-field')
|
||||
})
|
||||
|
||||
it('has the placeholder "input-field-placeholder"', () => {
|
||||
expect(wrapper.find('input').attributes('placeholder')).toEqual('input-field-placeholder')
|
||||
})
|
||||
|
||||
it('has the value ""', () => {
|
||||
expect(wrapper.vm.currentValue).toEqual('')
|
||||
})
|
||||
|
||||
it('has the label "input-field-label"', () => {
|
||||
expect(wrapper.find('label').text()).toEqual('input-field-label')
|
||||
})
|
||||
|
||||
it('has the label for "input-field-name-input-field"', () => {
|
||||
expect(wrapper.find('label').attributes('for')).toEqual('input-field-name-input-field')
|
||||
})
|
||||
})
|
||||
|
||||
describe('input value changes', () => {
|
||||
it('emits input with new value', async () => {
|
||||
await wrapper.find('input').setValue('12')
|
||||
expect(wrapper.emitted('input')).toBeTruthy()
|
||||
expect(wrapper.emitted('input')).toEqual([['12']])
|
||||
})
|
||||
})
|
||||
|
||||
describe('value property changes', () => {
|
||||
it('updates data model', async () => {
|
||||
await wrapper.setProps({ value: 'user@example.org' })
|
||||
expect(wrapper.vm.currentValue).toEqual('user@example.org')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
73
frontend/src/components/Inputs/InputEmail.vue
Normal file
73
frontend/src/components/Inputs/InputEmail.vue
Normal file
@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<validation-provider
|
||||
tag="div"
|
||||
:rules="rules"
|
||||
:name="name"
|
||||
v-slot="{ errors, valid, validated, ariaInput, ariaMsg }"
|
||||
>
|
||||
<b-form-group :label="label" :label-for="labelFor">
|
||||
<b-input-group>
|
||||
<b-form-input
|
||||
v-model="currentValue"
|
||||
v-bind="ariaInput"
|
||||
:id="labelFor"
|
||||
:name="name"
|
||||
:placeholder="placeholder"
|
||||
type="email"
|
||||
:state="validated ? valid : false"
|
||||
trim
|
||||
class="email-form-input"
|
||||
></b-form-input>
|
||||
<b-form-invalid-feedback v-bind="ariaMsg">
|
||||
{{ errors[0] }}
|
||||
</b-form-invalid-feedback>
|
||||
</b-input-group>
|
||||
</b-form-group>
|
||||
</validation-provider>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'InputEmail',
|
||||
props: {
|
||||
rules: {
|
||||
default: () => {
|
||||
return {
|
||||
required: true,
|
||||
email: true,
|
||||
}
|
||||
},
|
||||
},
|
||||
name: { type: String, default: 'Email' },
|
||||
label: { type: String, default: 'Email' },
|
||||
placeholder: { type: String, default: 'Email' },
|
||||
value: { required: true, type: String },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentValue: '',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
labelFor() {
|
||||
return this.name + '-input-field'
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
currentValue() {
|
||||
this.$emit('input', this.currentValue)
|
||||
},
|
||||
value() {
|
||||
if (this.value !== this.currentValue) this.currentValue = this.value
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.email-form-input {
|
||||
border-right-style: solid !important;
|
||||
border-right-width: 1px !important;
|
||||
padding-right: 12px !important;
|
||||
border-top-right-radius: 6px !important;
|
||||
border-bottom-right-radius: 6px !important;
|
||||
}
|
||||
</style>
|
||||
98
frontend/src/components/Inputs/InputPassword.spec.js
Normal file
98
frontend/src/components/Inputs/InputPassword.spec.js
Normal file
@ -0,0 +1,98 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
|
||||
import InputPassword from './InputPassword'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
describe('InputPassword', () => {
|
||||
let wrapper
|
||||
|
||||
const propsData = {
|
||||
name: 'input-field-name',
|
||||
label: 'input-field-label',
|
||||
placeholder: 'input-field-placeholder',
|
||||
value: '',
|
||||
}
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(InputPassword, { localVue, propsData })
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('has an input field', () => {
|
||||
expect(wrapper.find('input').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
describe('properties', () => {
|
||||
it('has the name "input-field-name"', () => {
|
||||
expect(wrapper.find('input').attributes('name')).toEqual('input-field-name')
|
||||
})
|
||||
|
||||
it('has the id "input-field-name-input-field"', () => {
|
||||
expect(wrapper.find('input').attributes('id')).toEqual('input-field-name-input-field')
|
||||
})
|
||||
|
||||
it('has the placeholder "input-field-placeholder"', () => {
|
||||
expect(wrapper.find('input').attributes('placeholder')).toEqual('input-field-placeholder')
|
||||
})
|
||||
|
||||
it('has the value ""', () => {
|
||||
expect(wrapper.vm.currentValue).toEqual('')
|
||||
})
|
||||
|
||||
it('has the label "input-field-label"', () => {
|
||||
expect(wrapper.find('label').text()).toEqual('input-field-label')
|
||||
})
|
||||
|
||||
it('has the label for "input-field-name-input-field"', () => {
|
||||
expect(wrapper.find('label').attributes('for')).toEqual('input-field-name-input-field')
|
||||
})
|
||||
})
|
||||
|
||||
describe('input value changes', () => {
|
||||
it('emits input with new value', async () => {
|
||||
await wrapper.find('input').setValue('12')
|
||||
expect(wrapper.emitted('input')).toBeTruthy()
|
||||
expect(wrapper.emitted('input')).toEqual([['12']])
|
||||
})
|
||||
})
|
||||
|
||||
describe('password visibilty', () => {
|
||||
it('has type password by default', () => {
|
||||
expect(wrapper.find('input').attributes('type')).toEqual('password')
|
||||
})
|
||||
|
||||
it('changes to type text when icon is clicked', async () => {
|
||||
await wrapper.find('button').trigger('click')
|
||||
expect(wrapper.find('input').attributes('type')).toEqual('text')
|
||||
})
|
||||
|
||||
it('changes back to type password when icon is clicked twice', async () => {
|
||||
await wrapper.find('button').trigger('click')
|
||||
await wrapper.find('button').trigger('click')
|
||||
expect(wrapper.find('input').attributes('type')).toEqual('password')
|
||||
})
|
||||
})
|
||||
|
||||
describe('password visibilty icon', () => {
|
||||
it('is by default bi-eye-slash', () => {
|
||||
expect(wrapper.find('svg').classes('bi-eye-slash')).toBe(true)
|
||||
})
|
||||
|
||||
it('changes to bi-eye when clicked', async () => {
|
||||
await wrapper.find('button').trigger('click')
|
||||
expect(wrapper.find('svg').classes('bi-eye')).toBe(true)
|
||||
})
|
||||
|
||||
it('changes back to bi-eye-slash when clicked twice', async () => {
|
||||
await wrapper.find('button').trigger('click')
|
||||
await wrapper.find('button').trigger('click')
|
||||
expect(wrapper.find('svg').classes('bi-eye-slash')).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
81
frontend/src/components/Inputs/InputPassword.vue
Normal file
81
frontend/src/components/Inputs/InputPassword.vue
Normal file
@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<validation-provider
|
||||
tag="div"
|
||||
:rules="rules"
|
||||
:name="name"
|
||||
:bails="!showAllErrors"
|
||||
:immediate="immediate"
|
||||
v-slot="{ errors, valid, validated, ariaInput, ariaMsg }"
|
||||
>
|
||||
<b-form-group :label="label" :label-for="labelFor">
|
||||
<b-input-group>
|
||||
<b-form-input
|
||||
v-model="currentValue"
|
||||
v-bind="ariaInput"
|
||||
:id="labelFor"
|
||||
:name="name"
|
||||
:placeholder="placeholder"
|
||||
:type="showPassword ? 'text' : 'password'"
|
||||
:state="validated ? valid : false"
|
||||
></b-form-input>
|
||||
<b-input-group-append>
|
||||
<b-button variant="outline-primary" @click="toggleShowPassword">
|
||||
<b-icon :icon="showPassword ? 'eye' : 'eye-slash'" />
|
||||
</b-button>
|
||||
</b-input-group-append>
|
||||
<b-form-invalid-feedback v-bind="ariaMsg">
|
||||
<div v-if="showAllErrors">
|
||||
<span v-for="error in errors" :key="error">
|
||||
{{ error }}
|
||||
<br />
|
||||
</span>
|
||||
</div>
|
||||
<div v-else>
|
||||
{{ errors[0] }}
|
||||
</div>
|
||||
</b-form-invalid-feedback>
|
||||
</b-input-group>
|
||||
</b-form-group>
|
||||
</validation-provider>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'InputPassword',
|
||||
props: {
|
||||
rules: {
|
||||
default: () => {
|
||||
return {
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
},
|
||||
name: { type: String, default: 'password' },
|
||||
label: { type: String, default: 'Password' },
|
||||
placeholder: { type: String, default: 'Password' },
|
||||
value: { required: true, type: String },
|
||||
showAllErrors: { type: Boolean, default: false },
|
||||
immediate: { type: Boolean, default: false },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentValue: '',
|
||||
showPassword: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
labelFor() {
|
||||
return this.name + '-input-field'
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleShowPassword() {
|
||||
this.showPassword = !this.showPassword
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
currentValue() {
|
||||
this.$emit('input', this.currentValue)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,64 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
|
||||
import InputPasswordConfirmation from './InputPasswordConfirmation'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
// validation is tested in src/views/Pages/UserProfile/UserCard_FormUserPasswort.spec.js
|
||||
|
||||
describe('InputPasswordConfirmation', () => {
|
||||
let wrapper
|
||||
|
||||
const propsData = {
|
||||
value: {
|
||||
password: '',
|
||||
passwordRepeat: '',
|
||||
},
|
||||
}
|
||||
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
}
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(InputPasswordConfirmation, { localVue, propsData, mocks })
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('has two input fields', () => {
|
||||
expect(wrapper.findAll('input')).toHaveLength(2)
|
||||
})
|
||||
|
||||
describe('input values ', () => {
|
||||
it('emits input with new value for first input field', async () => {
|
||||
await wrapper.findAll('input').at(0).setValue('1234')
|
||||
expect(wrapper.emitted('input')).toBeTruthy()
|
||||
expect(wrapper.emitted('input')).toEqual([
|
||||
[
|
||||
{
|
||||
password: '1234',
|
||||
passwordRepeat: '',
|
||||
},
|
||||
],
|
||||
])
|
||||
})
|
||||
|
||||
it('emits input with new value for second input field', async () => {
|
||||
await wrapper.findAll('input').at(1).setValue('1234')
|
||||
expect(wrapper.emitted('input')).toBeTruthy()
|
||||
expect(wrapper.emitted('input')).toEqual([
|
||||
[
|
||||
{
|
||||
password: '',
|
||||
passwordRepeat: '1234',
|
||||
},
|
||||
],
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
68
frontend/src/components/Inputs/InputPasswordConfirmation.vue
Normal file
68
frontend/src/components/Inputs/InputPasswordConfirmation.vue
Normal file
@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<div>
|
||||
<b-row class="mb-2">
|
||||
<b-col>
|
||||
<input-password
|
||||
:rules="{
|
||||
required: true,
|
||||
containsLowercaseCharacter: true,
|
||||
containsUppercaseCharacter: true,
|
||||
containsNumericCharacter: true,
|
||||
atLeastEightCharactera: true,
|
||||
}"
|
||||
:label="$t('form.password_new')"
|
||||
:showAllErrors="true"
|
||||
:immediate="true"
|
||||
:name="$t('form.password_new')"
|
||||
:placeholder="$t('form.password_new')"
|
||||
v-model="password"
|
||||
></input-password>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row class="mb-2">
|
||||
<b-col>
|
||||
<input-password
|
||||
:rules="{ samePassword: value.password }"
|
||||
:label="$t('form.password_new_repeat')"
|
||||
:placeholder="$t('form.password_new_repeat')"
|
||||
v-model="passwordRepeat"
|
||||
></input-password>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import InputPassword from './InputPassword'
|
||||
|
||||
export default {
|
||||
name: 'InputPasswordConfirm',
|
||||
components: {
|
||||
InputPassword,
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
password: '',
|
||||
passwordRepeat: '',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
passwordObject() {
|
||||
return { password: this.password, passwordRepeat: this.passwordRepeat }
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
password() {
|
||||
this.$emit('input', this.passwordObject)
|
||||
},
|
||||
passwordRepeat() {
|
||||
this.$emit('input', this.passwordObject)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -6,7 +6,7 @@
|
||||
<b-icon icon="chevron-left" variant="primary"></b-icon>
|
||||
</b-button>
|
||||
</b-col>
|
||||
<b-col cols="2">
|
||||
<b-col cols="3">
|
||||
<p class="text-center pt-2">{{ currentPage }} / {{ totalPages }}</p>
|
||||
</b-col>
|
||||
<b-col>
|
||||
|
||||
@ -18,7 +18,25 @@
|
||||
"de": "Deutsch",
|
||||
"en": "English"
|
||||
},
|
||||
"decay": "Vergänglichkeit",
|
||||
"decay": {
|
||||
"decay": "Vergänglichkeit",
|
||||
"decay_since_last_transaction":"Vergänglichkeit seit der letzten Transaktion",
|
||||
"calculation_decay":"Berechnung der Vergänglichkeit",
|
||||
"Starting_block_decay":"Startblock Vergänglichkeit",
|
||||
"decay_introduced":"Die Vergänglichkeit wurde Eingeführt am",
|
||||
"last_transaction":"Letzte Transaktion",
|
||||
"past_time":"Vergangene Zeit",
|
||||
"since_introduction":"seit Einführung der Vergänglichkeit",
|
||||
"year":"Jahre",
|
||||
"months":"Monate",
|
||||
"days":"Tage",
|
||||
"hours":"Stunden",
|
||||
"minutes":"Minuten",
|
||||
"seconds":"Sekunden",
|
||||
"received":"empfangen",
|
||||
"sent":"gesendet",
|
||||
"created":"geschöpft"
|
||||
},
|
||||
"form": {
|
||||
"cancel": "Abbrechen",
|
||||
"reset": "Zurücksetzen",
|
||||
@ -66,6 +84,7 @@
|
||||
},
|
||||
"error": {
|
||||
"error":"Fehler",
|
||||
"no-account": "Leider konnten wir keinen Account finden mit diesen Daten!",
|
||||
"change-password": "Fehler beim Ändern des Passworts"
|
||||
},
|
||||
"transaction":{
|
||||
@ -140,6 +159,7 @@
|
||||
},
|
||||
"reset-password": {
|
||||
"title": "Passwort zurücksetzen",
|
||||
"text": "Jetzt kannst du ein neues Passwort speichern, mit dem du dich zukünftig in der Gradido-App anmelden kannst."
|
||||
"text": "Jetzt kannst du ein neues Passwort speichern, mit dem du dich zukünftig in der Gradido-App anmelden kannst.",
|
||||
"not-authenticated": "Leider konnten wir dich nicht authentifizieren. Bitte wende dich an den Support."
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,7 +18,25 @@
|
||||
"de": "Deutsch",
|
||||
"en": "English"
|
||||
},
|
||||
"decay": "Decay",
|
||||
"decay": {
|
||||
"decay": "Decay",
|
||||
"decay_since_last_transaction":"Decay since the last transaction",
|
||||
"calculation_decay": "Calculation of Decay",
|
||||
"Starting_block_decay": "Starting Block Decay",
|
||||
"decay_introduced": "Decay was Introduced on",
|
||||
"last_transaction": "Last transaction:",
|
||||
"past_time": "Past time",
|
||||
"since_introduction": "Since the introduction of Decay",
|
||||
"year": "Years",
|
||||
"months": "Months",
|
||||
"days": "Days",
|
||||
"hours": "Hours",
|
||||
"minutes": "Minutes",
|
||||
"seconds": "Seconds",
|
||||
"received":"received",
|
||||
"sent":"sent",
|
||||
"created":"created"
|
||||
},
|
||||
"form": {
|
||||
"cancel":"Cancel",
|
||||
"reset": "Reset",
|
||||
@ -66,6 +84,7 @@
|
||||
},
|
||||
"error": {
|
||||
"error":"Error",
|
||||
"no-account": "Unfortunately we could not find an account to the given data!",
|
||||
"change-password": "Error while changing password"
|
||||
},
|
||||
"transaction":{
|
||||
@ -141,6 +160,7 @@
|
||||
},
|
||||
"reset-password": {
|
||||
"title": "Reset Password",
|
||||
"text": "Now you can save a new password to login to the Gradido-App in the future."
|
||||
"text": "Now you can save a new password to login to the Gradido-App in the future.",
|
||||
"not-authenticated": "Unfortunately we could not authenticate you. Please contact the support."
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,22 +2,18 @@ import Vue from 'vue'
|
||||
import DashboardPlugin from './plugins/dashboard-plugin'
|
||||
import App from './App.vue'
|
||||
import i18n from './i18n.js'
|
||||
import { configure, extend } from 'vee-validate'
|
||||
// eslint-disable-next-line camelcase
|
||||
import { required, email, min, max, is_not } from 'vee-validate/dist/rules'
|
||||
import { loadAllRules } from './validation-rules'
|
||||
|
||||
// store
|
||||
import { store } from './store/store'
|
||||
|
||||
import loginAPI from './apis/loginAPI'
|
||||
|
||||
// router setup
|
||||
import router from './routes/router'
|
||||
|
||||
// plugin setup
|
||||
Vue.use(DashboardPlugin)
|
||||
Vue.config.productionTip = false
|
||||
|
||||
loadAllRules(i18n)
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
if (to.meta.requiresAuth && !store.state.sessionId) {
|
||||
next({ path: '/login' })
|
||||
@ -26,68 +22,6 @@ router.beforeEach((to, from, next) => {
|
||||
}
|
||||
})
|
||||
|
||||
configure({
|
||||
defaultMessage: (field, values) => {
|
||||
values._field_ = i18n.t(`fields.${field}`)
|
||||
return i18n.t(`validations.messages.${values._rule_}`, values)
|
||||
},
|
||||
})
|
||||
|
||||
extend('email', {
|
||||
...email,
|
||||
message: (_, values) => i18n.t('validations.messages.email', values),
|
||||
})
|
||||
|
||||
extend('required', {
|
||||
...required,
|
||||
message: (_, values) => i18n.t('validations.messages.required', values),
|
||||
})
|
||||
|
||||
extend('min', {
|
||||
...min,
|
||||
message: (_, values) => i18n.t('validations.messages.min', values),
|
||||
})
|
||||
|
||||
extend('max', {
|
||||
...max,
|
||||
message: (_, values) => i18n.t('validations.messages.max', values),
|
||||
})
|
||||
|
||||
extend('gddSendAmount', {
|
||||
validate(value, { min, max }) {
|
||||
value = value.replace(',', '.')
|
||||
return value.match(/^[0-9]+(\.[0-9]{0,2})?$/) && Number(value) >= min && Number(value) <= max
|
||||
},
|
||||
params: ['min', 'max'],
|
||||
message: (_, values) => {
|
||||
values.min = i18n.n(values.min, 'ungroupedDecimal')
|
||||
values.max = i18n.n(values.max, 'ungroupedDecimal')
|
||||
return i18n.t('form.validation.gddSendAmount', values)
|
||||
},
|
||||
})
|
||||
|
||||
extend('gddUsernameUnique', {
|
||||
async validate(value) {
|
||||
const result = await loginAPI.checkUsername(value)
|
||||
return result.result.data.state === 'success'
|
||||
},
|
||||
message: (_, values) => i18n.t('form.validation.usernmae-unique', values),
|
||||
})
|
||||
|
||||
extend('gddUsernameRgex', {
|
||||
validate(value) {
|
||||
return !!value.match(/^[a-zA-Z][-_a-zA-Z0-9]{2,}$/)
|
||||
},
|
||||
message: (_, values) => i18n.t('form.validation.usernmae-regex', values),
|
||||
})
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
extend('is_not', {
|
||||
// eslint-disable-next-line camelcase
|
||||
...is_not,
|
||||
message: (_, values) => i18n.t('form.validation.is-not', values),
|
||||
})
|
||||
|
||||
/* eslint-disable no-new */
|
||||
new Vue({
|
||||
el: '#app',
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
import '@/polyfills'
|
||||
import { configure, extend } from 'vee-validate'
|
||||
import GlobalComponents from './globalComponents'
|
||||
import GlobalDirectives from './globalDirectives'
|
||||
import SideBar from '@/components/SidebarPlugin'
|
||||
|
||||
import PortalVue from 'portal-vue'
|
||||
|
||||
import VueBootstrapToasts from 'vue-bootstrap-toasts'
|
||||
import Toasted from 'vue-toasted'
|
||||
|
||||
// vue-bootstrap
|
||||
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
|
||||
@ -14,8 +13,6 @@ import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
|
||||
// asset imports
|
||||
import '@/assets/scss/argon.scss'
|
||||
import '@/assets/vendor/nucleo/css/nucleo.css'
|
||||
import * as rules from 'vee-validate/dist/rules'
|
||||
import { messages } from 'vee-validate/dist/locale/en.json'
|
||||
|
||||
import VueQrcodeReader from 'vue-qrcode-reader'
|
||||
import VueQrcode from 'vue-qrcode'
|
||||
@ -28,13 +25,6 @@ import VueMoment from 'vue-moment'
|
||||
import Loading from 'vue-loading-overlay'
|
||||
import 'vue-loading-overlay/dist/vue-loading.css'
|
||||
|
||||
Object.keys(rules).forEach((rule) => {
|
||||
extend(rule, {
|
||||
...rules[rule], // copies rule configuration
|
||||
message: messages[rule], // assign message
|
||||
})
|
||||
})
|
||||
|
||||
export default {
|
||||
install(Vue) {
|
||||
Vue.use(GlobalComponents)
|
||||
@ -43,17 +33,20 @@ export default {
|
||||
Vue.use(PortalVue)
|
||||
Vue.use(BootstrapVue)
|
||||
Vue.use(IconsPlugin)
|
||||
Vue.use(VueBootstrapToasts)
|
||||
Vue.use(VueMoment)
|
||||
Vue.use(VueQrcodeReader)
|
||||
Vue.use(VueQrcode)
|
||||
Vue.use(FlatPickr)
|
||||
Vue.use(Loading)
|
||||
configure({
|
||||
classes: {
|
||||
valid: 'is-valid',
|
||||
invalid: 'is-invalid',
|
||||
dirty: ['is-dirty', 'is-dirty'], // multiple classes per flag!
|
||||
Vue.use(Toasted, {
|
||||
position: 'top-center',
|
||||
duration: 5000,
|
||||
fullWidth: true,
|
||||
action: {
|
||||
text: 'x',
|
||||
onClick: (e, toastObject) => {
|
||||
toastObject.goAway(0)
|
||||
},
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { mutations, actions } from './store'
|
||||
|
||||
const { language, email, sessionId } = mutations
|
||||
const { language, email, sessionId, username, firstName, lastName, description } = mutations
|
||||
const { login, logout } = actions
|
||||
|
||||
describe('Vuex store', () => {
|
||||
@ -28,51 +28,102 @@ describe('Vuex store', () => {
|
||||
expect(state.sessionId).toEqual('1234')
|
||||
})
|
||||
})
|
||||
|
||||
describe('username', () => {
|
||||
it('sets the state of username', () => {
|
||||
const state = { username: null }
|
||||
username(state, 'user')
|
||||
expect(state.username).toEqual('user')
|
||||
})
|
||||
})
|
||||
|
||||
describe('firstName', () => {
|
||||
it('sets the state of firstName', () => {
|
||||
const state = { firstName: null }
|
||||
firstName(state, 'Peter')
|
||||
expect(state.firstName).toEqual('Peter')
|
||||
})
|
||||
})
|
||||
|
||||
describe('lastName', () => {
|
||||
it('sets the state of lastName', () => {
|
||||
const state = { lastName: null }
|
||||
lastName(state, 'Lustig')
|
||||
expect(state.lastName).toEqual('Lustig')
|
||||
})
|
||||
})
|
||||
|
||||
describe('description', () => {
|
||||
it('sets the state of description', () => {
|
||||
const state = { description: null }
|
||||
description(state, 'Nickelbrille')
|
||||
expect(state.description).toEqual('Nickelbrille')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('actions', () => {
|
||||
describe('login', () => {
|
||||
const commit = jest.fn()
|
||||
const state = {}
|
||||
const commitedData = {
|
||||
sessionId: 1234,
|
||||
user: {
|
||||
email: 'someone@there.is',
|
||||
language: 'en',
|
||||
username: 'user',
|
||||
first_name: 'Peter',
|
||||
last_name: 'Lustig',
|
||||
description: 'Nickelbrille',
|
||||
},
|
||||
}
|
||||
|
||||
it('calls three commits', () => {
|
||||
login(
|
||||
{ commit, state },
|
||||
{ sessionId: 1234, user: { email: 'someone@there.is', language: 'en' } },
|
||||
)
|
||||
it('calls seven commits', () => {
|
||||
login({ commit, state }, commitedData)
|
||||
expect(commit).toHaveBeenCalledTimes(7)
|
||||
})
|
||||
|
||||
it('commits sessionId', () => {
|
||||
login(
|
||||
{ commit, state },
|
||||
{ sessionId: 1234, user: { email: 'someone@there.is', language: 'en' } },
|
||||
)
|
||||
login({ commit, state }, commitedData)
|
||||
expect(commit).toHaveBeenNthCalledWith(1, 'sessionId', 1234)
|
||||
})
|
||||
|
||||
it('commits email', () => {
|
||||
login(
|
||||
{ commit, state },
|
||||
{ sessionId: 1234, user: { email: 'someone@there.is', language: 'en' } },
|
||||
)
|
||||
login({ commit, state }, commitedData)
|
||||
expect(commit).toHaveBeenNthCalledWith(2, 'email', 'someone@there.is')
|
||||
})
|
||||
|
||||
it('commits language', () => {
|
||||
login(
|
||||
{ commit, state },
|
||||
{ sessionId: 1234, user: { email: 'someone@there.is', language: 'en' } },
|
||||
)
|
||||
login({ commit, state }, commitedData)
|
||||
expect(commit).toHaveBeenNthCalledWith(3, 'language', 'en')
|
||||
})
|
||||
|
||||
it('commits username', () => {
|
||||
login({ commit, state }, commitedData)
|
||||
expect(commit).toHaveBeenNthCalledWith(4, 'username', 'user')
|
||||
})
|
||||
|
||||
it('commits firstName', () => {
|
||||
login({ commit, state }, commitedData)
|
||||
expect(commit).toHaveBeenNthCalledWith(5, 'firstName', 'Peter')
|
||||
})
|
||||
|
||||
it('commits lastName', () => {
|
||||
login({ commit, state }, commitedData)
|
||||
expect(commit).toHaveBeenNthCalledWith(6, 'lastName', 'Lustig')
|
||||
})
|
||||
|
||||
it('commits description', () => {
|
||||
login({ commit, state }, commitedData)
|
||||
expect(commit).toHaveBeenNthCalledWith(7, 'description', 'Nickelbrille')
|
||||
})
|
||||
})
|
||||
|
||||
describe('logout', () => {
|
||||
const commit = jest.fn()
|
||||
const state = {}
|
||||
|
||||
it('calls two commits', () => {
|
||||
it('calls six commits', () => {
|
||||
logout({ commit, state })
|
||||
expect(commit).toHaveBeenCalledTimes(6)
|
||||
})
|
||||
@ -87,11 +138,36 @@ describe('Vuex store', () => {
|
||||
expect(commit).toHaveBeenNthCalledWith(2, 'email', null)
|
||||
})
|
||||
|
||||
// how can I get this working?
|
||||
it.skip('calls sessionStorage.clear()', () => {
|
||||
it('commits username', () => {
|
||||
logout({ commit, state })
|
||||
const spy = jest.spyOn(sessionStorage, 'clear')
|
||||
expect(spy).toHaveBeenCalledTimes(1)
|
||||
expect(commit).toHaveBeenNthCalledWith(3, 'username', '')
|
||||
})
|
||||
|
||||
it('commits firstName', () => {
|
||||
logout({ commit, state })
|
||||
expect(commit).toHaveBeenNthCalledWith(4, 'firstName', '')
|
||||
})
|
||||
|
||||
it('commits lastName', () => {
|
||||
logout({ commit, state })
|
||||
expect(commit).toHaveBeenNthCalledWith(5, 'lastName', '')
|
||||
})
|
||||
|
||||
it('commits description', () => {
|
||||
logout({ commit, state })
|
||||
expect(commit).toHaveBeenNthCalledWith(6, 'description', '')
|
||||
})
|
||||
|
||||
// how to get this working?
|
||||
it.skip('calls sessionStorage.clear()', () => {
|
||||
const clearStorageMock = jest.fn()
|
||||
global.sessionStorage = jest.fn(() => {
|
||||
return {
|
||||
clear: clearStorageMock,
|
||||
}
|
||||
})
|
||||
logout({ commit, state })
|
||||
expect(clearStorageMock).toBeCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
110
frontend/src/validation-rules.js
Normal file
110
frontend/src/validation-rules.js
Normal file
@ -0,0 +1,110 @@
|
||||
import { configure, extend } from 'vee-validate'
|
||||
// eslint-disable-next-line camelcase
|
||||
import { required, email, min, max, is_not } from 'vee-validate/dist/rules'
|
||||
import loginAPI from './apis/loginAPI'
|
||||
|
||||
export const loadAllRules = (i18nCallback) => {
|
||||
configure({
|
||||
defaultMessage: (field, values) => {
|
||||
values._field_ = i18nCallback.t(`fields.${field}`)
|
||||
return i18nCallback.t(`validations.messages.${values._rule_}`, values)
|
||||
},
|
||||
classes: {
|
||||
valid: 'is-valid',
|
||||
invalid: 'is-invalid',
|
||||
dirty: ['is-dirty', 'is-dirty'], // multiple classes per flag!
|
||||
},
|
||||
})
|
||||
|
||||
extend('email', {
|
||||
...email,
|
||||
message: (_, values) => i18nCallback.t('validations.messages.email', values),
|
||||
})
|
||||
|
||||
extend('required', {
|
||||
...required,
|
||||
message: (_, values) => i18nCallback.t('validations.messages.required', values),
|
||||
})
|
||||
|
||||
extend('min', {
|
||||
...min,
|
||||
message: (_, values) => i18nCallback.t('validations.messages.min', values),
|
||||
})
|
||||
|
||||
extend('max', {
|
||||
...max,
|
||||
message: (_, values) => i18nCallback.t('validations.messages.max', values),
|
||||
})
|
||||
|
||||
extend('gddSendAmount', {
|
||||
validate(value, { min, max }) {
|
||||
value = value.replace(',', '.')
|
||||
return value.match(/^[0-9]+(\.[0-9]{0,2})?$/) && Number(value) >= min && Number(value) <= max
|
||||
},
|
||||
params: ['min', 'max'],
|
||||
message: (_, values) => {
|
||||
values.min = i18nCallback.n(values.min, 'ungroupedDecimal')
|
||||
values.max = i18nCallback.n(values.max, 'ungroupedDecimal')
|
||||
return i18nCallback.t('form.validation.gddSendAmount', values)
|
||||
},
|
||||
})
|
||||
|
||||
extend('gddUsernameUnique', {
|
||||
async validate(value) {
|
||||
const result = await loginAPI.checkUsername(value)
|
||||
return result.result.data.state === 'success'
|
||||
},
|
||||
message: (_, values) => i18nCallback.t('form.validation.usernmae-unique', values),
|
||||
})
|
||||
|
||||
extend('gddUsernameRgex', {
|
||||
validate(value) {
|
||||
return !!value.match(/^[a-zA-Z][-_a-zA-Z0-9]{2,}$/)
|
||||
},
|
||||
message: (_, values) => i18nCallback.t('form.validation.usernmae-regex', values),
|
||||
})
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
extend('is_not', {
|
||||
// eslint-disable-next-line camelcase
|
||||
...is_not,
|
||||
message: (_, values) => i18nCallback.t('form.validation.is-not', values),
|
||||
})
|
||||
|
||||
// Password validation
|
||||
|
||||
extend('containsLowercaseCharacter', {
|
||||
validate(value) {
|
||||
return !!value.match(/[a-z]+/)
|
||||
},
|
||||
message: (_, values) => i18nCallback.t('site.signup.lowercase', values),
|
||||
})
|
||||
|
||||
extend('containsUppercaseCharacter', {
|
||||
validate(value) {
|
||||
return !!value.match(/[A-Z]+/)
|
||||
},
|
||||
message: (_, values) => i18nCallback.t('site.signup.uppercase', values),
|
||||
})
|
||||
|
||||
extend('containsNumericCharacter', {
|
||||
validate(value) {
|
||||
return !!value.match(/[0-9]+/)
|
||||
},
|
||||
message: (_, values) => i18nCallback.t('site.signup.one_number', values),
|
||||
})
|
||||
|
||||
extend('atLeastEightCharactera', {
|
||||
validate(value) {
|
||||
return !!value.match(/.{8,}/)
|
||||
},
|
||||
message: (_, values) => i18nCallback.t('site.signup.minimum', values),
|
||||
})
|
||||
|
||||
extend('samePassword', {
|
||||
validate(value, [pwd]) {
|
||||
return value === pwd
|
||||
},
|
||||
message: (_, values) => i18nCallback.t('site.signup.dont_match', values),
|
||||
})
|
||||
}
|
||||
@ -34,7 +34,11 @@
|
||||
</span>
|
||||
<b-media-body class="ml-2">
|
||||
<span class="avatar">
|
||||
<vue-qrcode :value="$store.state.email" type="image/png"></vue-qrcode>
|
||||
<vue-qrcode
|
||||
v-if="$store.state.email"
|
||||
:value="$store.state.email"
|
||||
type="image/png"
|
||||
></vue-qrcode>
|
||||
</span>
|
||||
</b-media-body>
|
||||
</b-media>
|
||||
|
||||
@ -1,34 +1,127 @@
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import AccountOverview from './AccountOverview'
|
||||
import communityAPI from '../../apis/communityAPI.js'
|
||||
|
||||
jest.mock('../../apis/communityAPI.js')
|
||||
|
||||
const sendMock = jest.fn()
|
||||
sendMock.mockReturnValue({ success: true })
|
||||
|
||||
communityAPI.send = sendMock
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
describe('AccountOverview', () => {
|
||||
let wrapper
|
||||
|
||||
const propsData = {
|
||||
balance: 123.45,
|
||||
transactionCount: 1,
|
||||
}
|
||||
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
$store: {
|
||||
state: {
|
||||
sessionId: 1,
|
||||
},
|
||||
},
|
||||
$n: jest.fn((n) => String(n)),
|
||||
}
|
||||
|
||||
const Wrapper = () => {
|
||||
return shallowMount(AccountOverview, { localVue, mocks })
|
||||
return mount(AccountOverview, { localVue, mocks, propsData })
|
||||
}
|
||||
|
||||
describe('shallow Mount', () => {
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('has a status line', () => {
|
||||
expect(wrapper.find('gdd-status-stub').exists()).toBeTruthy()
|
||||
expect(wrapper.find('div.gdd-status').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('has a send field', () => {
|
||||
expect(wrapper.find('gdd-send-stub').exists()).toBeTruthy()
|
||||
expect(wrapper.find('div.gdd-send').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('has a transactions table', () => {
|
||||
expect(wrapper.find('gdd-transaction-list-stub').exists()).toBeTruthy()
|
||||
expect(wrapper.find('div.gdd-transaction-list').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
describe('transaction form', () => {
|
||||
it('steps forward in the dialog', async () => {
|
||||
await wrapper.findComponent({ name: 'TransactionForm' }).vm.$emit('set-transaction', {
|
||||
email: 'user@example.org',
|
||||
amount: 23.45,
|
||||
memo: 'Make the best of it!',
|
||||
})
|
||||
expect(wrapper.findComponent({ name: 'TransactionConfirmation' }).exists()).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('confirm transaction', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.setData({
|
||||
currentTransactionStep: 1,
|
||||
transactionData: {
|
||||
email: 'user@example.org',
|
||||
amount: 23.45,
|
||||
memo: 'Make the best of it!',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('resets the transaction process when on-reset is emitted', async () => {
|
||||
await wrapper.findComponent({ name: 'TransactionConfirmation' }).vm.$emit('on-reset')
|
||||
expect(wrapper.findComponent({ name: 'TransactionForm' }).exists()).toBeTruthy()
|
||||
expect(wrapper.vm.transactionData).toEqual({
|
||||
email: '',
|
||||
amount: 0,
|
||||
memo: '',
|
||||
})
|
||||
})
|
||||
|
||||
describe('transaction is confirmed and server response is success', () => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
await wrapper
|
||||
.findComponent({ name: 'TransactionConfirmation' })
|
||||
.vm.$emit('send-transaction')
|
||||
})
|
||||
|
||||
it('calls the API when send-transaction is emitted', async () => {
|
||||
expect(sendMock).toBeCalledWith(1, {
|
||||
email: 'user@example.org',
|
||||
amount: 23.45,
|
||||
memo: 'Make the best of it!',
|
||||
})
|
||||
})
|
||||
|
||||
it('emits update-balance', () => {
|
||||
expect(wrapper.emitted('update-balance')).toBeTruthy()
|
||||
expect(wrapper.emitted('update-balance')).toEqual([[23.45]])
|
||||
})
|
||||
|
||||
it('shows the succes page', () => {
|
||||
expect(wrapper.find('div.card-body').text()).toContain('form.send_transaction_success')
|
||||
})
|
||||
})
|
||||
|
||||
describe('transaction is confirmed and server response is error', () => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
sendMock.mockReturnValue({ success: false })
|
||||
await wrapper
|
||||
.findComponent({ name: 'TransactionConfirmation' })
|
||||
.vm.$emit('send-transaction')
|
||||
})
|
||||
|
||||
it('shows the error page', () => {
|
||||
expect(wrapper.find('div.card-body').text()).toContain('form.send_transaction_error')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -30,7 +30,7 @@
|
||||
<gdd-transaction-list
|
||||
v-if="showContext"
|
||||
:transactions="transactions"
|
||||
:page-size="5"
|
||||
:pageSize="5"
|
||||
:timestamp="timestamp"
|
||||
:transaction-count="transactionCount"
|
||||
@update-transactions="updateTransactions"
|
||||
@ -69,7 +69,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
timestamp: Date.now(),
|
||||
transactionData: EMPTY_TRANSACTION_DATA,
|
||||
transactionData: { ...EMPTY_TRANSACTION_DATA },
|
||||
error: false,
|
||||
currentTransactionStep: 0,
|
||||
loading: false,
|
||||
@ -94,7 +94,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
setTransaction(data) {
|
||||
this.transactionData = data
|
||||
this.transactionData = { ...data }
|
||||
this.currentTransactionStep = 1
|
||||
},
|
||||
async sendTransaction() {
|
||||
@ -110,7 +110,7 @@ export default {
|
||||
this.loading = false
|
||||
},
|
||||
onReset() {
|
||||
this.transactionData = EMPTY_TRANSACTION_DATA
|
||||
this.transactionData = { ...EMPTY_TRANSACTION_DATA }
|
||||
this.currentTransactionStep = 0
|
||||
},
|
||||
updateTransactions(pagination) {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import TransactionForm from './TransactionForm'
|
||||
import flushPromises from 'flush-promises'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
@ -19,8 +20,12 @@ describe('GddSend', () => {
|
||||
},
|
||||
}
|
||||
|
||||
const propsData = {
|
||||
balance: 100.0,
|
||||
}
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(TransactionForm, { localVue, mocks })
|
||||
return mount(TransactionForm, { localVue, mocks, propsData })
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
@ -53,6 +58,18 @@ describe('GddSend', () => {
|
||||
'E-Mail',
|
||||
)
|
||||
})
|
||||
|
||||
it('flushes an error message when no valid email is given', async () => {
|
||||
await wrapper.find('#input-group-1').find('input').setValue('a')
|
||||
await flushPromises()
|
||||
expect(wrapper.find('span.errors').text()).toBe('validations.messages.email')
|
||||
})
|
||||
|
||||
it('trims the email after blur', async () => {
|
||||
await wrapper.find('#input-group-1').find('input').setValue(' valid@email.com ')
|
||||
await flushPromises()
|
||||
expect(wrapper.vm.form.email).toBe('valid@email.com')
|
||||
})
|
||||
})
|
||||
|
||||
describe('ammount field', () => {
|
||||
@ -73,6 +90,24 @@ describe('GddSend', () => {
|
||||
'0.01',
|
||||
)
|
||||
})
|
||||
|
||||
it('flushes an error message when no valid amount is given', async () => {
|
||||
await wrapper.find('#input-group-2').find('input').setValue('a')
|
||||
await flushPromises()
|
||||
expect(wrapper.find('span.errors').text()).toBe('form.validation.gddSendAmount')
|
||||
})
|
||||
|
||||
it('flushes an error message when amount is too high', async () => {
|
||||
await wrapper.find('#input-group-2').find('input').setValue('123.34')
|
||||
await flushPromises()
|
||||
expect(wrapper.find('span.errors').text()).toBe('form.validation.gddSendAmount')
|
||||
})
|
||||
|
||||
it('flushes no errors when amount is valid', async () => {
|
||||
await wrapper.find('#input-group-2').find('input').setValue('87.34')
|
||||
await flushPromises()
|
||||
expect(wrapper.find('span.errors').exists()).toBeFalsy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('message text box', () => {
|
||||
@ -89,6 +124,18 @@ describe('GddSend', () => {
|
||||
it('has a label form.memo', () => {
|
||||
expect(wrapper.find('label.input-3').text()).toBe('form.memo')
|
||||
})
|
||||
|
||||
it('flushes an error message when memo is less than 5 characters', async () => {
|
||||
await wrapper.find('#input-group-3').find('textarea').setValue('a')
|
||||
await flushPromises()
|
||||
expect(wrapper.find('span.errors').text()).toBe('validations.messages.min')
|
||||
})
|
||||
|
||||
it('flushes no error message when memo is valid', async () => {
|
||||
await wrapper.find('#input-group-3').find('textarea').setValue('Long enough')
|
||||
await flushPromises()
|
||||
expect(wrapper.find('span.errors').exists()).toBeFalsy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('cancel button', () => {
|
||||
@ -100,11 +147,42 @@ describe('GddSend', () => {
|
||||
expect(wrapper.find('button[type="reset"]').text()).toBe('form.reset')
|
||||
})
|
||||
|
||||
it.skip('clears the email field on click', async () => {
|
||||
wrapper.find('#input-group-1').find('input').setValue('someone@watches.tv')
|
||||
wrapper.find('button[type="reset"]').trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.vm.form.email).toBeNull()
|
||||
it('clears all fields on click', async () => {
|
||||
await wrapper.find('#input-group-1').find('input').setValue('someone@watches.tv')
|
||||
await wrapper.find('#input-group-2').find('input').setValue('87.23')
|
||||
await wrapper.find('#input-group-3').find('textarea').setValue('Long enugh')
|
||||
await flushPromises()
|
||||
expect(wrapper.vm.form.email).toBe('someone@watches.tv')
|
||||
expect(wrapper.vm.form.amount).toBe('87.23')
|
||||
expect(wrapper.vm.form.memo).toBe('Long enugh')
|
||||
await wrapper.find('button[type="reset"]').trigger('click')
|
||||
await flushPromises()
|
||||
expect(wrapper.vm.form.email).toBe('')
|
||||
expect(wrapper.vm.form.amount).toBe('')
|
||||
expect(wrapper.vm.form.memo).toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
describe('submit', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.find('#input-group-1').find('input').setValue('someone@watches.tv')
|
||||
await wrapper.find('#input-group-2').find('input').setValue('87.23')
|
||||
await wrapper.find('#input-group-3').find('textarea').setValue('Long enugh')
|
||||
await wrapper.find('form').trigger('submit')
|
||||
await flushPromises()
|
||||
})
|
||||
|
||||
it('emits set-transaction', async () => {
|
||||
expect(wrapper.emitted('set-transaction')).toBeTruthy()
|
||||
expect(wrapper.emitted('set-transaction')).toEqual([
|
||||
[
|
||||
{
|
||||
email: 'someone@watches.tv',
|
||||
amount: 87.23,
|
||||
memo: 'Long enugh',
|
||||
},
|
||||
],
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -168,7 +168,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
onSubmit() {
|
||||
this.normalizeAmount()
|
||||
this.normalizeAmount(true)
|
||||
this.$emit('set-transaction', {
|
||||
email: this.form.email,
|
||||
amount: this.form.amountValue,
|
||||
@ -181,10 +181,11 @@ export default {
|
||||
this.form.amount = ''
|
||||
this.form.memo = ''
|
||||
},
|
||||
setTransaction(data) {
|
||||
this.form.email = data.email
|
||||
this.form.amount = data.amount
|
||||
},
|
||||
/*
|
||||
setTransaction(data) {
|
||||
this.form.email = data.email
|
||||
this.form.amount = data.amount
|
||||
}, */
|
||||
normalizeAmount(isValid) {
|
||||
this.amountFocused = false
|
||||
if (!isValid) return
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="gdd-status">
|
||||
<b-row>
|
||||
<b-col class="p-0">
|
||||
<b-card class="p-0" style="background-color: #ebebeba3 !important">
|
||||
|
||||
@ -7,6 +7,10 @@ const errorHandler = jest.fn()
|
||||
|
||||
localVue.config.errorHandler = errorHandler
|
||||
|
||||
const scrollToMock = jest.fn()
|
||||
|
||||
global.scrollTo = scrollToMock
|
||||
|
||||
describe('GddTransactionList', () => {
|
||||
let wrapper
|
||||
|
||||
@ -199,7 +203,7 @@ describe('GddTransactionList', () => {
|
||||
})
|
||||
|
||||
it('shows the name of the receiver', () => {
|
||||
expect(transaction.findAll('div').at(3).text()).toBe('decay')
|
||||
expect(transaction.findAll('div').at(3).text()).toBe('decay.decay_since_last_transaction')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -263,29 +267,30 @@ describe('GddTransactionList', () => {
|
||||
})
|
||||
|
||||
it('emits update-transactions when next button is clicked', async () => {
|
||||
paginationButtons.find('button.next-page').trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
await paginationButtons.find('button.next-page').trigger('click')
|
||||
expect(wrapper.emitted('update-transactions')[1]).toEqual([{ firstPage: 2, items: 25 }])
|
||||
})
|
||||
|
||||
it('shows text "2 / 2" when next button is clicked', async () => {
|
||||
paginationButtons.find('button.next-page').trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
await paginationButtons.find('button.next-page').trigger('click')
|
||||
expect(paginationButtons.find('p.text-center').text()).toBe('2 / 2')
|
||||
})
|
||||
|
||||
it('has next-button disabled when next button is clicked', async () => {
|
||||
paginationButtons.find('button.next-page').trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
await paginationButtons.find('button.next-page').trigger('click')
|
||||
expect(paginationButtons.find('button.next-page').attributes('disabled')).toBe('disabled')
|
||||
})
|
||||
|
||||
it('scrolls to top after loading next page', async () => {
|
||||
await paginationButtons.find('button.next-page').trigger('click')
|
||||
expect(scrollToMock).toBeCalled()
|
||||
})
|
||||
|
||||
it('emits update-transactions when preivous button is clicked after next buton', async () => {
|
||||
paginationButtons.find('button.next-page').trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
paginationButtons.find('button.previous-page').trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
await paginationButtons.find('button.next-page').trigger('click')
|
||||
await paginationButtons.find('button.previous-page').trigger('click')
|
||||
expect(wrapper.emitted('update-transactions')[2]).toEqual([{ firstPage: 1, items: 25 }])
|
||||
expect(scrollToMock).toBeCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -2,69 +2,79 @@
|
||||
<div class="gdd-transaction-list">
|
||||
<b-list-group>
|
||||
<b-list-group-item
|
||||
v-for="item in transactions"
|
||||
:key="item.id"
|
||||
style="background-color: #ebebeba3 !important"
|
||||
v-for="{ decay, transaction_id, type, date, balance, name, memo } in transactions"
|
||||
:key="transaction_id"
|
||||
:style="type === 'decay' ? 'background-color:#f1e0ae3d' : ''"
|
||||
>
|
||||
<div class="d-flex gdd-transaction-list-item" v-b-toggle="'a' + item.date + ''">
|
||||
<!-- ROW Start -->
|
||||
<div class="d-flex gdd-transaction-list-item" v-b-toggle="'a' + date + ''">
|
||||
<!-- ICON -->
|
||||
<div style="width: 8%">
|
||||
<b-icon :icon="getProperties(item).icon" :class="getProperties(item).class" />
|
||||
<b-icon :icon="getProperties(type).icon" :class="getProperties(type).class" />
|
||||
</div>
|
||||
<!-- Text Links -->
|
||||
<div class="font1_2em pr-2 text-right" style="width: 32%">
|
||||
<span>{{ getProperties(item).operator }}</span>
|
||||
{{ $n(item.balance, 'decimal') }}
|
||||
<span>{{ getProperties(type).operator }}</span>
|
||||
<small v-if="type === 'decay'">{{ $n(balance, 'decimal') }}</small>
|
||||
|
||||
<span v-else>{{ $n(balance, 'decimal') }}</span>
|
||||
|
||||
<div v-if="decay">
|
||||
<br />
|
||||
<b-icon v-if="type != 'decay'" icon="droplet-half" height="15" class="mb-3" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- Text Rechts -->
|
||||
<div class="font1_2em text-left pl-2" style="width: 55%">
|
||||
{{ item.name ? item.name : $t('decay') }}
|
||||
<div v-if="item.date" class="text-sm">{{ $d($moment(item.date), 'long') }}</div>
|
||||
{{ name ? name : '' }}
|
||||
<span v-if="type === 'decay'">
|
||||
<small>{{ $t('decay.decay_since_last_transaction') }}</small>
|
||||
</span>
|
||||
<div v-if="date" class="text-sm">{{ $d($moment(date), 'long') }}</div>
|
||||
<decay-information v-if="decay" decaytyp="short" :decay="decay" />
|
||||
</div>
|
||||
<div class="text-right" style="width: 5%">
|
||||
<!-- Collaps Toggle Button -->
|
||||
<div v-if="type != 'decay'" class="text-right" style="width: 5%">
|
||||
<b-button class="btn-sm">
|
||||
<b>i</b>
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
<b-collapse :id="'a' + item.date + ''" class="mt-2">
|
||||
<b-card>
|
||||
<b-list-group>
|
||||
<b-list-group-item v-if="item.type === 'send'">
|
||||
<b-badge class="mr-4" variant="primary" pill>{{ $t('form.receiver') }}</b-badge>
|
||||
{{ item.name }}
|
||||
</b-list-group-item>
|
||||
<b-list-group-item v-else>
|
||||
<b-badge class="mr-4" variant="primary" pill>{{ $t('form.sender') }}</b-badge>
|
||||
{{ item.name }}
|
||||
</b-list-group-item>
|
||||
|
||||
<b-list-group-item>
|
||||
<b-badge class="mr-4" variant="primary" pill>type</b-badge>
|
||||
{{ item.type }}
|
||||
</b-list-group-item>
|
||||
<b-list-group-item>
|
||||
<b-badge class="mr-5" variant="primary" pill>id</b-badge>
|
||||
{{ item.transaction_id }}
|
||||
</b-list-group-item>
|
||||
<b-list-group-item>
|
||||
<b-badge class="mr-4" variant="primary" pill>{{ $t('form.date') }}</b-badge>
|
||||
{{ item.date }}
|
||||
</b-list-group-item>
|
||||
<b-list-group-item>
|
||||
<b-badge class="mr-4" variant="primary" pill>gdd</b-badge>
|
||||
{{ item.balance }}
|
||||
</b-list-group-item>
|
||||
<b-list-group-item>
|
||||
<b-badge class="mr-4" variant="primary" pill>{{ $t('form.memo') }}</b-badge>
|
||||
{{ item.memo }}
|
||||
</b-list-group-item>
|
||||
</b-list-group>
|
||||
<b-button v-b-toggle="'collapse-1-inner' + item.date" variant="secondary">
|
||||
{{ $t('transaction.more') }}
|
||||
</b-button>
|
||||
<b-collapse :id="'collapse-1-inner' + item.date" class="mt-2">
|
||||
<b-card>{{ item }}</b-card>
|
||||
</b-collapse>
|
||||
</b-card>
|
||||
<!-- ROW End -->
|
||||
<!-- Collaps Start -->
|
||||
<b-collapse v-if="type != 'decay'" :id="'a' + date + ''">
|
||||
<b-list-group v-if="type === 'receive' || type === 'send'">
|
||||
<b-list-group-item style="border: 0px; background-color: #f1f1f1">
|
||||
<div class="d-flex">
|
||||
<div style="width: 40%" class="text-right pr-3 mr-2">
|
||||
{{ type === 'receive' ? 'von:' : 'an:' }}
|
||||
</div>
|
||||
<div style="width: 60%">
|
||||
{{ name }}
|
||||
<b-avatar class="mr-3"></b-avatar>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<div style="width: 40%" class="text-right pr-3 mr-2">
|
||||
{{ type === 'receive' ? 'Nachricht:' : 'Nachricht:' }}
|
||||
</div>
|
||||
<div style="width: 60%">
|
||||
{{ memo }}
|
||||
</div>
|
||||
</div>
|
||||
</b-list-group-item>
|
||||
</b-list-group>
|
||||
<b-list-group v-if="type === 'creation'">
|
||||
<b-list-group-item style="border: 0px">
|
||||
<div class="d-flex">
|
||||
<div style="width: 40%" class="text-right pr-3 mr-2">Schöpfung</div>
|
||||
<div style="width: 60%">Aus der Community</div>
|
||||
</div>
|
||||
</b-list-group-item>
|
||||
</b-list-group>
|
||||
<decay-information v-if="decay" decaytyp="new" :decay="decay" />
|
||||
</b-collapse>
|
||||
<!-- Collaps End -->
|
||||
</b-list-group-item>
|
||||
<pagination-buttons
|
||||
v-if="showPagination && transactionCount > pageSize"
|
||||
@ -84,6 +94,7 @@
|
||||
|
||||
<script>
|
||||
import PaginationButtons from '../../../components/PaginationButtons'
|
||||
import DecayInformation from '../../../components/DecayInformation'
|
||||
|
||||
const iconsByType = {
|
||||
send: { icon: 'arrow-left-circle', classes: 'text-danger', operator: '-' },
|
||||
@ -96,10 +107,12 @@ export default {
|
||||
name: 'gdd-transaction-list',
|
||||
components: {
|
||||
PaginationButtons,
|
||||
DecayInformation,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentPage: 1,
|
||||
startDecay: 0,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
@ -133,8 +146,8 @@ export default {
|
||||
items: this.pageSize,
|
||||
})
|
||||
},
|
||||
getProperties(item) {
|
||||
const type = iconsByType[item.type]
|
||||
getProperties(givenType) {
|
||||
const type = iconsByType[givenType]
|
||||
if (type)
|
||||
return {
|
||||
icon: type.icon,
|
||||
@ -149,10 +162,12 @@ export default {
|
||||
showNext() {
|
||||
this.currentPage++
|
||||
this.updateTransactions()
|
||||
window.scrollTo(0, 0)
|
||||
},
|
||||
showPrevious() {
|
||||
this.currentPage--
|
||||
this.updateTransactions()
|
||||
window.scrollTo(0, 0)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -83,9 +83,7 @@ describe('ForgotPassword', () => {
|
||||
})
|
||||
|
||||
it('displays an error', () => {
|
||||
expect(form.find('#reset-pwd--live-feedback').text()).toEqual(
|
||||
'The Email field must be a valid email',
|
||||
)
|
||||
expect(form.find('div.invalid-feedback').text()).toEqual('validations.messages.email')
|
||||
})
|
||||
|
||||
it('does not call the API', () => {
|
||||
@ -96,8 +94,7 @@ describe('ForgotPassword', () => {
|
||||
describe('valid Email', () => {
|
||||
beforeEach(async () => {
|
||||
await form.find('input').setValue('user@example.org')
|
||||
form.trigger('submit')
|
||||
await wrapper.vm.$nextTick()
|
||||
await form.trigger('submit')
|
||||
await flushPromises()
|
||||
})
|
||||
|
||||
|
||||
@ -19,27 +19,7 @@
|
||||
<b-card-body class="p-4">
|
||||
<validation-observer ref="observer" v-slot="{ handleSubmit }">
|
||||
<b-form role="form" @submit.prevent="handleSubmit(onSubmit)">
|
||||
<validation-provider
|
||||
name="Email"
|
||||
:rules="{ required: true, email: true }"
|
||||
v-slot="validationContext"
|
||||
>
|
||||
<b-form-group class="mb-3" label="Email" label-for="input-reset-pwd">
|
||||
<b-form-input
|
||||
id="input-reset-pwd"
|
||||
name="input-reset-pwd"
|
||||
v-model="form.email"
|
||||
placeholder="Email"
|
||||
:state="getValidationState(validationContext)"
|
||||
aria-describedby="reset-pwd--live-feedback"
|
||||
></b-form-input>
|
||||
|
||||
<b-form-invalid-feedback id="reset-pwd--live-feedback">
|
||||
{{ validationContext.errors[0] }}
|
||||
</b-form-invalid-feedback>
|
||||
</b-form-group>
|
||||
</validation-provider>
|
||||
|
||||
<input-email v-model="form.email"></input-email>
|
||||
<div class="text-center">
|
||||
<b-button type="submit" variant="primary">
|
||||
{{ $t('site.password.reset_now') }}
|
||||
@ -59,9 +39,13 @@
|
||||
</template>
|
||||
<script>
|
||||
import loginAPI from '../../apis/loginAPI.js'
|
||||
import InputEmail from '../../components/Inputs/InputEmail'
|
||||
|
||||
export default {
|
||||
name: 'password',
|
||||
components: {
|
||||
InputEmail,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
disable: 'disabled',
|
||||
@ -71,9 +55,6 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getValidationState({ dirty, validated, valid = null }) {
|
||||
return dirty || validated ? valid : null
|
||||
},
|
||||
async onSubmit() {
|
||||
await loginAPI.sendEmail(this.form.email)
|
||||
// always give success to avoid email spying
|
||||
|
||||
@ -1,10 +1,37 @@
|
||||
import { mount, RouterLinkStub } from '@vue/test-utils'
|
||||
import flushPromises from 'flush-promises'
|
||||
|
||||
import loginAPI from '../../apis/loginAPI'
|
||||
import Login from './Login'
|
||||
|
||||
jest.mock('../../apis/loginAPI')
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const mockLoginCall = jest.fn()
|
||||
mockLoginCall.mockReturnValue({
|
||||
success: true,
|
||||
result: {
|
||||
data: {
|
||||
session_id: 1,
|
||||
user: {
|
||||
name: 'Peter Lustig',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
loginAPI.login = mockLoginCall
|
||||
|
||||
const toastErrorMock = jest.fn()
|
||||
const mockStoreDispach = jest.fn()
|
||||
const mockRouterPush = jest.fn()
|
||||
const spinnerHideMock = jest.fn()
|
||||
const spinnerMock = jest.fn(() => {
|
||||
return {
|
||||
hide: spinnerHideMock,
|
||||
}
|
||||
})
|
||||
|
||||
describe('Login', () => {
|
||||
let wrapper
|
||||
|
||||
@ -13,6 +40,18 @@ describe('Login', () => {
|
||||
locale: 'en',
|
||||
},
|
||||
$t: jest.fn((t) => t),
|
||||
$store: {
|
||||
dispatch: mockStoreDispach,
|
||||
},
|
||||
$loading: {
|
||||
show: spinnerMock,
|
||||
},
|
||||
$router: {
|
||||
push: mockRouterPush,
|
||||
},
|
||||
$toasted: {
|
||||
error: toastErrorMock,
|
||||
},
|
||||
}
|
||||
|
||||
const stubs = {
|
||||
@ -76,16 +115,76 @@ describe('Login', () => {
|
||||
it('has a Submit button', () => {
|
||||
expect(wrapper.find('button[type="submit"]').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('shows a warning when no valid Email is entered', async () => {
|
||||
wrapper.find('input[placeholder="Email"]').setValue('no_valid@Email')
|
||||
await flushPromises()
|
||||
await expect(wrapper.find('.invalid-feedback').text()).toEqual(
|
||||
'The Email field must be a valid email',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
// to do: test submit button
|
||||
describe('submit', () => {
|
||||
describe('no data', () => {
|
||||
it('displays a message that Email is required', async () => {
|
||||
await wrapper.find('form').trigger('submit')
|
||||
await flushPromises()
|
||||
expect(wrapper.findAll('div.invalid-feedback').at(0).text()).toBe(
|
||||
'validations.messages.required',
|
||||
)
|
||||
})
|
||||
|
||||
it('displays a message that password is required', async () => {
|
||||
await wrapper.find('form').trigger('submit')
|
||||
await flushPromises()
|
||||
expect(wrapper.findAll('div.invalid-feedback').at(1).text()).toBe(
|
||||
'validations.messages.required',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('valid data', () => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
await wrapper.find('input[placeholder="Email"]').setValue('user@example.org')
|
||||
await wrapper.find('input[placeholder="form.password"]').setValue('1234')
|
||||
await flushPromises()
|
||||
await wrapper.find('form').trigger('submit')
|
||||
await flushPromises()
|
||||
})
|
||||
|
||||
it('calls the API with the given data', () => {
|
||||
expect(mockLoginCall).toBeCalledWith('user@example.org', '1234')
|
||||
})
|
||||
|
||||
it('creates a spinner', () => {
|
||||
expect(spinnerMock).toBeCalled()
|
||||
})
|
||||
|
||||
describe('login success', () => {
|
||||
it('dispatches server response to store', () => {
|
||||
expect(mockStoreDispach).toBeCalledWith('login', {
|
||||
sessionId: 1,
|
||||
user: { name: 'Peter Lustig' },
|
||||
})
|
||||
})
|
||||
|
||||
it('redirects to overview page', () => {
|
||||
expect(mockRouterPush).toBeCalledWith('/overview')
|
||||
})
|
||||
|
||||
it('hides the spinner', () => {
|
||||
expect(spinnerHideMock).toBeCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('login fails', () => {
|
||||
beforeEach(() => {
|
||||
mockLoginCall.mockReturnValue({ success: false })
|
||||
})
|
||||
|
||||
it('hides the spinner', () => {
|
||||
expect(spinnerHideMock).toBeCalled()
|
||||
})
|
||||
|
||||
it('toasts an error message', () => {
|
||||
expect(toastErrorMock).toBeCalledWith('error.no-account')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -13,7 +13,6 @@
|
||||
</div>
|
||||
</b-container>
|
||||
</div>
|
||||
<!-- Page content -->
|
||||
<b-container class="mt--8">
|
||||
<b-row class="justify-content-center">
|
||||
<b-col lg="5" md="7">
|
||||
@ -22,76 +21,16 @@
|
||||
<div class="text-center text-muted mb-4">
|
||||
<small>{{ $t('login') }}</small>
|
||||
</div>
|
||||
|
||||
<validation-observer ref="observer" v-slot="{ handleSubmit }">
|
||||
<b-form @submit.stop.prevent="handleSubmit(onSubmit)">
|
||||
<validation-provider
|
||||
name="Email"
|
||||
:rules="{ required: true, email: true }"
|
||||
v-slot="validationContext"
|
||||
>
|
||||
<b-form-group class="mb-3" label="Email" label-for="login-email">
|
||||
<b-form-input
|
||||
id="login-email"
|
||||
name="example-input-1"
|
||||
v-model="form.email"
|
||||
placeholder="Email"
|
||||
:state="getValidationState(validationContext)"
|
||||
aria-describedby="login-email-live-feedback"
|
||||
></b-form-input>
|
||||
|
||||
<b-form-invalid-feedback id="login-email-live-feedback">
|
||||
{{ validationContext.errors[0] }}
|
||||
</b-form-invalid-feedback>
|
||||
</b-form-group>
|
||||
</validation-provider>
|
||||
|
||||
<validation-provider
|
||||
<input-email v-model="form.email"></input-email>
|
||||
<input-password
|
||||
:label="$t('form.password')"
|
||||
:placeholder="$t('form.password')"
|
||||
:name="$t('form.password')"
|
||||
:rules="{ required: true }"
|
||||
v-slot="validationContext"
|
||||
>
|
||||
<b-form-group
|
||||
class="mb-5"
|
||||
id="example-input-group-1"
|
||||
:label="$t('form.password')"
|
||||
label-for="example-input-1"
|
||||
>
|
||||
<b-input-group>
|
||||
<b-form-input
|
||||
id="input-pwd"
|
||||
name="input-pwd"
|
||||
v-model="form.password"
|
||||
:placeholder="$t('form.password')"
|
||||
:type="passwordVisible ? 'text' : 'password'"
|
||||
:state="getValidationState(validationContext)"
|
||||
aria-describedby="input-2-live-feedback"
|
||||
></b-form-input>
|
||||
|
||||
<b-input-group-append>
|
||||
<b-button variant="outline-primary" @click="togglePasswordVisibility">
|
||||
<b-icon :icon="passwordVisible ? 'eye' : 'eye-slash'" />
|
||||
</b-button>
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
<b-form-invalid-feedback id="input-2-live-feedback">
|
||||
{{ validationContext.errors[0] }}
|
||||
</b-form-invalid-feedback>
|
||||
</b-form-group>
|
||||
</validation-provider>
|
||||
|
||||
<b-alert v-show="loginfail" show dismissible variant="warning">
|
||||
<span class="alert-text bv-example-row">
|
||||
<b-row>
|
||||
<b-col class="col-9 text-left text-dark">
|
||||
<strong>
|
||||
Leider konnten wir keinen Account finden mit diesen Daten!
|
||||
</strong>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</span>
|
||||
</b-alert>
|
||||
<div class="text-center">
|
||||
v-model="form.password"
|
||||
></input-password>
|
||||
<div class="text-center mt-4">
|
||||
<b-button type="submit" variant="primary">{{ $t('login') }}</b-button>
|
||||
</div>
|
||||
</b-form>
|
||||
@ -118,32 +57,27 @@
|
||||
<script>
|
||||
import loginAPI from '../../apis/loginAPI'
|
||||
import CONFIG from '../../config'
|
||||
import InputPassword from '../../components/Inputs/InputPassword'
|
||||
import InputEmail from '../../components/Inputs/InputEmail'
|
||||
|
||||
export default {
|
||||
name: 'login',
|
||||
components: {
|
||||
InputPassword,
|
||||
InputEmail,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
email: '',
|
||||
password: '',
|
||||
// rememberMe: false
|
||||
},
|
||||
loginfail: false,
|
||||
allowRegister: CONFIG.ALLOW_REGISTER,
|
||||
passwordVisible: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getValidationState({ dirty, validated, valid = null }) {
|
||||
return dirty || validated ? valid : null
|
||||
},
|
||||
|
||||
togglePasswordVisibility() {
|
||||
this.passwordVisible = !this.passwordVisible
|
||||
},
|
||||
async onSubmit() {
|
||||
// error info ausschalten
|
||||
this.loginfail = false
|
||||
const loader = this.$loading.show({
|
||||
container: this.$refs.submitButton,
|
||||
})
|
||||
@ -157,7 +91,7 @@ export default {
|
||||
loader.hide()
|
||||
} else {
|
||||
loader.hide()
|
||||
this.loginfail = true
|
||||
this.$toasted.error(this.$t('error.no-account'))
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@ -84,7 +84,7 @@ describe('Register', () => {
|
||||
wrapper.find('#registerEmail').setValue('no_valid@Email')
|
||||
await flushPromises()
|
||||
await expect(wrapper.find('#registerEmailLiveFeedback').text()).toEqual(
|
||||
'The Email field must be a valid email',
|
||||
'validations.messages.email',
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@ -3,6 +3,8 @@ import loginAPI from '../../apis/loginAPI'
|
||||
import ResetPassword from './ResetPassword'
|
||||
import flushPromises from 'flush-promises'
|
||||
|
||||
// validation is tested in src/views/Pages/UserProfile/UserCard_FormUserPasswort.spec.js
|
||||
|
||||
jest.mock('../../apis/loginAPI')
|
||||
|
||||
const localVue = global.localVue
|
||||
@ -28,6 +30,7 @@ emailVerificationMock
|
||||
.mockReturnValueOnce({ success: false, result: { message: 'error' } })
|
||||
.mockReturnValueOnce({ success: false, result: { message: 'error' } })
|
||||
.mockReturnValueOnce({ success: false, result: { message: 'error' } })
|
||||
.mockReturnValueOnce({ success: false, result: { message: 'error' } })
|
||||
.mockReturnValue(successResponseObject)
|
||||
|
||||
changePasswordMock
|
||||
@ -50,7 +53,7 @@ describe('ResetPassword', () => {
|
||||
optin: '123',
|
||||
},
|
||||
},
|
||||
$toast: {
|
||||
$toasted: {
|
||||
error: toasterMock,
|
||||
},
|
||||
$router: {
|
||||
@ -81,36 +84,39 @@ describe('ResetPassword', () => {
|
||||
})
|
||||
|
||||
it('does not render the Reset Password form when not authenticated', () => {
|
||||
expect(wrapper.find('div.resetpwd-form').exists()).toBeFalsy()
|
||||
expect(wrapper.find('form').exists()).toBeFalsy()
|
||||
})
|
||||
|
||||
it('toasts an error when no valid optin is given', () => {
|
||||
expect(toasterMock).toHaveBeenCalledWith('error')
|
||||
})
|
||||
|
||||
it('has a message suggesting to contact the support', () => {
|
||||
expect(wrapper.find('div.header').text()).toContain('reset-password.title')
|
||||
expect(wrapper.find('div.header').text()).toContain('reset-password.not-authenticated')
|
||||
})
|
||||
|
||||
it('renders the Reset Password form when authenticated', async () => {
|
||||
wrapper.setData({ authenticated: true })
|
||||
await wrapper.vm.$nextTick()
|
||||
await wrapper.setData({ authenticated: true })
|
||||
expect(wrapper.find('div.resetpwd-form').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
describe('Register header', () => {
|
||||
it('has a welcome message', () => {
|
||||
expect(wrapper.find('div.header').text()).toBe('reset-password.title reset-password.text')
|
||||
expect(wrapper.find('div.header').text()).toContain('reset-password.title')
|
||||
expect(wrapper.find('div.header').text()).toContain('reset-password.text')
|
||||
})
|
||||
})
|
||||
|
||||
/* there is no back button, why?
|
||||
describe('links', () => {
|
||||
it('has a link "Back"', () => {
|
||||
expect(wrapper.findAllComponents(RouterLinkStub).at(0).text()).toEqual('back')
|
||||
})
|
||||
|
||||
it('links to /login when clicking "Back"', () => {
|
||||
expect(wrapper.findAllComponents(RouterLinkStub).at(0).props().to).toBe('/login')
|
||||
expect(wrapper.findAllComponents(RouterLinkStub).at(0).props().to).toBe('/Login')
|
||||
})
|
||||
})
|
||||
*/
|
||||
|
||||
describe('reset password form', () => {
|
||||
it('has a register form', () => {
|
||||
@ -121,10 +127,6 @@ describe('ResetPassword', () => {
|
||||
expect(wrapper.findAll('input[type="password"]').length).toBe(2)
|
||||
})
|
||||
|
||||
it('has no submit button when not completely filled', () => {
|
||||
expect(wrapper.find('button[type="submit"]').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('toggles the first input field to text when eye icon is clicked', async () => {
|
||||
wrapper.findAll('button').at(0).trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
@ -140,23 +142,20 @@ describe('ResetPassword', () => {
|
||||
|
||||
describe('submit form', () => {
|
||||
beforeEach(async () => {
|
||||
wrapper.findAll('input').at(0).setValue('Aa123456')
|
||||
wrapper.findAll('input').at(1).setValue('Aa123456')
|
||||
await wrapper.vm.$nextTick()
|
||||
await wrapper.findAll('input').at(0).setValue('Aa123456')
|
||||
await wrapper.findAll('input').at(1).setValue('Aa123456')
|
||||
await flushPromises()
|
||||
wrapper.find('form').trigger('submit')
|
||||
await wrapper.find('form').trigger('submit')
|
||||
})
|
||||
|
||||
describe('server response with error', () => {
|
||||
it('toasts an error message', async () => {
|
||||
it('toasts an error message', () => {
|
||||
expect(toasterMock).toHaveBeenCalledWith('error')
|
||||
})
|
||||
})
|
||||
|
||||
describe('server response with success', () => {
|
||||
it('calls the API', async () => {
|
||||
await wrapper.vm.$nextTick()
|
||||
await flushPromises()
|
||||
it('calls the API', () => {
|
||||
expect(changePasswordMock).toHaveBeenCalledWith(1, 'user@example.org', 'Aa123456')
|
||||
})
|
||||
|
||||
|
||||
@ -1,102 +1,34 @@
|
||||
<template>
|
||||
<div class="resetpwd-form" v-if="authenticated">
|
||||
<!-- Header -->
|
||||
<div class="header p-4">
|
||||
<b-container class="container">
|
||||
<div class="resetpwd-form">
|
||||
<b-container>
|
||||
<div class="header p-4" ref="header">
|
||||
<div class="header-body text-center mb-7">
|
||||
<b-row class="justify-content-center">
|
||||
<b-col xl="5" lg="6" md="8" class="px-2">
|
||||
<h1>{{ $t('reset-password.title') }}</h1>
|
||||
<div class="pb-4">{{ $t('reset-password.text') }}</div>
|
||||
<div class="pb-4" v-if="!pending">
|
||||
<span v-if="authenticated">
|
||||
{{ $t('reset-password.text') }}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ $t('reset-password.not-authenticated') }}
|
||||
</span>
|
||||
</div>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</div>
|
||||
</b-container>
|
||||
</div>
|
||||
<!-- Page content -->
|
||||
</div>
|
||||
</b-container>
|
||||
<b-container class="mt--8 p-1">
|
||||
<!-- Table -->
|
||||
<b-row class="justify-content-center">
|
||||
<b-row class="justify-content-center" v-if="authenticated">
|
||||
<b-col lg="6" md="8">
|
||||
<b-card no-body class="border-0" style="background-color: #ebebeba3 !important">
|
||||
<b-card-body class="p-4">
|
||||
<validation-observer ref="observer" v-slot="{ handleSubmit }">
|
||||
<b-form role="form" @submit.prevent="handleSubmit(onSubmit)">
|
||||
<validation-provider
|
||||
:name="$t('form.password')"
|
||||
:rules="{ required: true }"
|
||||
v-slot="validationContext"
|
||||
>
|
||||
<b-form-group
|
||||
class="mb-5"
|
||||
:label="$t('form.password')"
|
||||
label-for="resetPassword"
|
||||
>
|
||||
<b-input-group>
|
||||
<b-form-input
|
||||
id="resetPassword"
|
||||
:name="$t('form.password')"
|
||||
v-model="form.password"
|
||||
:placeholder="$t('form.password')"
|
||||
:type="passwordVisible ? 'text' : 'password'"
|
||||
:state="getValidationState(validationContext)"
|
||||
aria-describedby="resetPasswordLiveFeedback"
|
||||
></b-form-input>
|
||||
|
||||
<b-input-group-append>
|
||||
<b-button variant="outline-primary" @click="togglePasswordVisibility">
|
||||
<b-icon :icon="passwordVisible ? 'eye' : 'eye-slash'" />
|
||||
</b-button>
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
<b-form-invalid-feedback id="resetPasswordLiveFeedback">
|
||||
{{ validationContext.errors[0] }}
|
||||
</b-form-invalid-feedback>
|
||||
</b-form-group>
|
||||
</validation-provider>
|
||||
|
||||
<b-form-group
|
||||
class="mb-5"
|
||||
:label="$t('form.passwordRepeat')"
|
||||
label-for="resetPasswordRepeat"
|
||||
>
|
||||
<b-input-group>
|
||||
<b-form-input
|
||||
id="resetPasswordRepeat"
|
||||
:name="$t('form.passwordRepeat')"
|
||||
v-model.lazy="form.passwordRepeat"
|
||||
:placeholder="$t('form.passwordRepeat')"
|
||||
:type="passwordVisibleRepeat ? 'text' : 'password'"
|
||||
></b-form-input>
|
||||
|
||||
<b-input-group-append>
|
||||
<b-button variant="outline-primary" @click="togglePasswordRepeatVisibility">
|
||||
<b-icon :icon="passwordVisibleRepeat ? 'eye' : 'eye-slash'" />
|
||||
</b-button>
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
</b-form-group>
|
||||
|
||||
<transition name="hint" appear>
|
||||
<div v-if="passwordValidation.errors.length > 0 && !submitted" class="hints">
|
||||
<ul>
|
||||
<li v-for="error in passwordValidation.errors" :key="error">
|
||||
<small>{{ error }}</small>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="matches" v-else-if="!samePasswords">
|
||||
<p>
|
||||
{{ $t('site.signup.dont_match') }}
|
||||
<i class="ni ni-active-40" color="danger"></i>
|
||||
</p>
|
||||
</div>
|
||||
</transition>
|
||||
<div
|
||||
class="text-center"
|
||||
v-if="passwordsFilled && samePasswords && passwordValidation.valid"
|
||||
>
|
||||
<b-button type="submit" variant="secondary" class="mt-4">
|
||||
<input-password-confirmation v-model="form" />
|
||||
<div class="text-center">
|
||||
<b-button type="submit" variant="primary" class="mt-4">
|
||||
{{ $t('reset') }}
|
||||
</b-button>
|
||||
</div>
|
||||
@ -106,38 +38,36 @@
|
||||
</b-card>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row>
|
||||
<b-col class="text-center py-lg-4">
|
||||
<router-link to="/Login" class="mt-3">{{ $t('back') }}</router-link>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import loginAPI from '../../apis/loginAPI'
|
||||
import InputPasswordConfirmation from '../../components/Inputs/InputPasswordConfirmation'
|
||||
|
||||
export default {
|
||||
name: 'reset',
|
||||
name: 'ResetPassword',
|
||||
components: {
|
||||
InputPasswordConfirmation,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
password: '',
|
||||
passwordRepeat: '',
|
||||
},
|
||||
password: '',
|
||||
passwordVisible: false,
|
||||
passwordVisibleRepeat: false,
|
||||
submitted: false,
|
||||
authenticated: false,
|
||||
sessionId: null,
|
||||
email: null,
|
||||
pending: true,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getValidationState({ dirty, validated, valid = null }) {
|
||||
return dirty || validated ? valid : null
|
||||
},
|
||||
togglePasswordVisibility() {
|
||||
this.passwordVisible = !this.passwordVisible
|
||||
},
|
||||
togglePasswordRepeatVisibility() {
|
||||
this.passwordVisibleRepeat = !this.passwordVisibleRepeat
|
||||
},
|
||||
async onSubmit() {
|
||||
const result = await loginAPI.changePassword(this.sessionId, this.email, this.form.password)
|
||||
if (result.success) {
|
||||
@ -150,12 +80,12 @@ export default {
|
||||
*/
|
||||
this.$router.push('/thx/reset')
|
||||
} else {
|
||||
this.$toast.error(result.result.message)
|
||||
this.$toasted.error(result.result.message)
|
||||
}
|
||||
},
|
||||
async authenticate() {
|
||||
const loader = this.$loading.show({
|
||||
container: this.$refs.submitButton,
|
||||
container: this.$refs.header,
|
||||
})
|
||||
const optin = this.$route.params.optin
|
||||
const result = await loginAPI.loginViaEmailVerificationCode(optin)
|
||||
@ -164,40 +94,13 @@ export default {
|
||||
this.sessionId = result.result.data.session_id
|
||||
this.email = result.result.data.user.email
|
||||
} else {
|
||||
this.$toast.error(result.result.message)
|
||||
this.$toasted.error(result.result.message)
|
||||
}
|
||||
loader.hide()
|
||||
this.pending = false
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
samePasswords() {
|
||||
return this.form.password === this.form.passwordRepeat
|
||||
},
|
||||
passwordsFilled() {
|
||||
return this.form.password !== '' && this.form.passwordRepeat !== ''
|
||||
},
|
||||
rules() {
|
||||
return [
|
||||
{ message: this.$t('site.signup.lowercase'), regex: /[a-z]+/ },
|
||||
{ message: this.$t('site.signup.uppercase'), regex: /[A-Z]+/ },
|
||||
{ message: this.$t('site.signup.minimum'), regex: /.{8,}/ },
|
||||
{ message: this.$t('site.signup.one_number'), regex: /[0-9]+/ },
|
||||
]
|
||||
},
|
||||
passwordValidation() {
|
||||
const errors = []
|
||||
for (const condition of this.rules) {
|
||||
if (!condition.regex.test(this.form.password)) {
|
||||
errors.push(condition.message)
|
||||
}
|
||||
}
|
||||
if (errors.length === 0) {
|
||||
return { valid: true, errors }
|
||||
}
|
||||
return { valid: false, errors }
|
||||
},
|
||||
},
|
||||
async created() {
|
||||
mounted() {
|
||||
this.authenticate()
|
||||
},
|
||||
}
|
||||
|
||||
@ -0,0 +1,169 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import UserCardFormUserData from './UserCard_FormUserData'
|
||||
import loginAPI from '../../../apis/loginAPI'
|
||||
import flushPromises from 'flush-promises'
|
||||
|
||||
jest.mock('../../../apis/loginAPI')
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const mockAPIcall = jest.fn((args) => {
|
||||
return { success: true }
|
||||
})
|
||||
|
||||
const toastErrorMock = jest.fn()
|
||||
const toastSuccessMock = jest.fn()
|
||||
const storeCommitMock = jest.fn()
|
||||
|
||||
loginAPI.updateUserInfos = mockAPIcall
|
||||
|
||||
describe('UserCard_FormUsername', () => {
|
||||
let wrapper
|
||||
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
$store: {
|
||||
state: {
|
||||
sessionId: 1,
|
||||
email: 'user@example.org',
|
||||
firstName: 'Peter',
|
||||
lastName: 'Lustig',
|
||||
description: '',
|
||||
},
|
||||
commit: storeCommitMock,
|
||||
},
|
||||
$toasted: {
|
||||
success: toastSuccessMock,
|
||||
error: toastErrorMock,
|
||||
},
|
||||
}
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(UserCardFormUserData, { localVue, mocks })
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('renders the component', () => {
|
||||
expect(wrapper.find('div#userdata_form').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('has an edit icon', () => {
|
||||
expect(wrapper.find('svg.bi-pencil').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('renders the first name', () => {
|
||||
expect(wrapper.findAll('div.col').at(2).text()).toBe('Peter')
|
||||
})
|
||||
|
||||
it('renders the last name', () => {
|
||||
expect(wrapper.findAll('div.col').at(4).text()).toBe('Lustig')
|
||||
})
|
||||
|
||||
it('renders the description', () => {
|
||||
expect(wrapper.findAll('div.col').at(6).text()).toBe('')
|
||||
})
|
||||
|
||||
describe('edit user data', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.find('svg.bi-pencil').trigger('click')
|
||||
})
|
||||
|
||||
it('shows an cancel icon', () => {
|
||||
expect(wrapper.find('svg.bi-x-circle').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('closes the input when cancel icon is clicked', async () => {
|
||||
await wrapper.find('svg.bi-x-circle').trigger('click')
|
||||
expect(wrapper.find('input').exists()).toBeFalsy()
|
||||
})
|
||||
|
||||
it('does not change the userdate when cancel is clicked', async () => {
|
||||
await wrapper.findAll('input').at(0).setValue('Petra')
|
||||
await wrapper.findAll('input').at(1).setValue('Lustiger')
|
||||
await wrapper.find('textarea').setValue('Keine Nickelbrille')
|
||||
await wrapper.find('svg.bi-x-circle').trigger('click')
|
||||
expect(wrapper.findAll('div.col').at(2).text()).toBe('Peter')
|
||||
expect(wrapper.findAll('div.col').at(4).text()).toBe('Lustig')
|
||||
expect(wrapper.findAll('div.col').at(6).text()).toBe('')
|
||||
})
|
||||
|
||||
it('has a submit button', () => {
|
||||
expect(wrapper.find('button[type="submit"]').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('does not enable submit button when data is not changed', async () => {
|
||||
await wrapper.find('form').trigger('keyup')
|
||||
expect(wrapper.find('button[type="submit"]').attributes('disabled')).toBe('disabled')
|
||||
})
|
||||
|
||||
describe('successfull submit', () => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
await wrapper.findAll('input').at(0).setValue('Petra')
|
||||
await wrapper.findAll('input').at(1).setValue('Lustiger')
|
||||
await wrapper.find('textarea').setValue('Keine Nickelbrille')
|
||||
await wrapper.find('form').trigger('keyup')
|
||||
await wrapper.find('button[type="submit"]').trigger('click')
|
||||
await flushPromises()
|
||||
})
|
||||
|
||||
it('calls the loginAPI', () => {
|
||||
expect(mockAPIcall).toBeCalledWith(1, 'user@example.org', {
|
||||
firstName: 'Petra',
|
||||
lastName: 'Lustiger',
|
||||
description: 'Keine Nickelbrille',
|
||||
})
|
||||
})
|
||||
|
||||
it('commits firstname to store', () => {
|
||||
expect(storeCommitMock).toBeCalledWith('firstName', 'Petra')
|
||||
})
|
||||
|
||||
it('commits lastname to store', () => {
|
||||
expect(storeCommitMock).toBeCalledWith('lastName', 'Lustiger')
|
||||
})
|
||||
|
||||
it('commits description to store', () => {
|
||||
expect(storeCommitMock).toBeCalledWith('description', 'Keine Nickelbrille')
|
||||
})
|
||||
|
||||
it('toasts a success message', () => {
|
||||
expect(toastSuccessMock).toBeCalledWith('site.profil.user-data.change-success')
|
||||
})
|
||||
|
||||
it('has an edit button again', () => {
|
||||
expect(wrapper.find('svg.bi-pencil').exists()).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('submit results in server error', () => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
mockAPIcall.mockReturnValue({ success: false, result: { message: 'Error' } })
|
||||
await wrapper.findAll('input').at(0).setValue('Petra')
|
||||
await wrapper.findAll('input').at(1).setValue('Lustiger')
|
||||
await wrapper.find('textarea').setValue('Keine Nickelbrille')
|
||||
await wrapper.find('form').trigger('keyup')
|
||||
await wrapper.find('button[type="submit"]').trigger('click')
|
||||
await flushPromises()
|
||||
})
|
||||
|
||||
it('calls the loginAPI', () => {
|
||||
expect(mockAPIcall).toBeCalledWith(1, 'user@example.org', {
|
||||
firstName: 'Petra',
|
||||
lastName: 'Lustiger',
|
||||
description: 'Keine Nickelbrille',
|
||||
})
|
||||
})
|
||||
|
||||
it('toasts an error message', () => {
|
||||
expect(toastErrorMock).toBeCalledWith('Error')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -135,9 +135,9 @@ export default {
|
||||
this.$store.commit('lastName', this.form.lastName)
|
||||
this.$store.commit('description', this.form.description)
|
||||
this.showUserData = true
|
||||
this.$toast.success(this.$t('site.profil.user-data.change-success'))
|
||||
this.$toasted.success(this.$t('site.profil.user-data.change-success'))
|
||||
} else {
|
||||
this.$toast.error(result.result.message)
|
||||
this.$toasted.error(result.result.message)
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import UserCardFormPasswort from './UserCard_FormUserPasswort'
|
||||
import loginAPI from '../../../apis/loginAPI'
|
||||
// import flushPromises from 'flush-promises'
|
||||
import flushPromises from 'flush-promises'
|
||||
|
||||
jest.mock('../../../apis/loginAPI')
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const changePasswordProfileMock = jest.fn()
|
||||
changePasswordProfileMock.mockReturnValue({ success: true })
|
||||
|
||||
loginAPI.changePasswordProfile = changePasswordProfileMock
|
||||
|
||||
const toastSuccessMock = jest.fn()
|
||||
@ -24,7 +26,7 @@ describe('UserCardFormUserPasswort', () => {
|
||||
email: 'user@example.org',
|
||||
},
|
||||
},
|
||||
$toast: {
|
||||
$toasted: {
|
||||
success: toastSuccessMock,
|
||||
error: toastErrorMock,
|
||||
},
|
||||
@ -59,8 +61,8 @@ describe('UserCardFormUserPasswort', () => {
|
||||
let form
|
||||
|
||||
beforeEach(async () => {
|
||||
wrapper.find('a').trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
await wrapper.find('a').trigger('click')
|
||||
await flushPromises()
|
||||
form = wrapper.find('form')
|
||||
})
|
||||
|
||||
@ -69,12 +71,11 @@ describe('UserCardFormUserPasswort', () => {
|
||||
})
|
||||
|
||||
it('has a cancel button', () => {
|
||||
expect(form.find('svg.bi-x-circle').exists()).toBeTruthy()
|
||||
expect(wrapper.find('svg.bi-x-circle').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('closes the form when cancel button is clicked', async () => {
|
||||
form.find('svg.bi-x-circle').trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
await wrapper.find('svg.bi-x-circle').trigger('click')
|
||||
expect(wrapper.find('input').exists()).toBeFalsy()
|
||||
})
|
||||
|
||||
@ -104,24 +105,103 @@ describe('UserCardFormUserPasswort', () => {
|
||||
expect(form.find('button[type="submit"]').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
/*
|
||||
describe('submit', () => {
|
||||
beforeEach(async () => {
|
||||
await form.findAll('input').at(0).setValue('1234')
|
||||
await form.findAll('input').at(1).setValue('Aa123456')
|
||||
await form.findAll('input').at(2).setValue('Aa123456')
|
||||
form.trigger('submit')
|
||||
await wrapper.vm.$nextTick()
|
||||
await flushPromises()
|
||||
describe('validation', () => {
|
||||
it('displays all password requirements', () => {
|
||||
const feedbackArray = wrapper.findAll('div.invalid-feedback').at(1).findAll('span')
|
||||
expect(feedbackArray).toHaveLength(5)
|
||||
expect(feedbackArray.at(0).text()).toBe('validations.messages.required')
|
||||
expect(feedbackArray.at(1).text()).toBe('site.signup.lowercase')
|
||||
expect(feedbackArray.at(2).text()).toBe('site.signup.uppercase')
|
||||
expect(feedbackArray.at(3).text()).toBe('site.signup.one_number')
|
||||
expect(feedbackArray.at(4).text()).toBe('site.signup.minimum')
|
||||
})
|
||||
|
||||
it('calls the API', async () => {
|
||||
await wrapper.vm.$nextTick()
|
||||
it('removes first message when a character is given', async () => {
|
||||
await wrapper.findAll('input').at(1).setValue('@')
|
||||
await flushPromises()
|
||||
expect(changePasswordProfileMock).toHaveBeenCalledWith(1, 'user@example.org', '1234', 'Aa123456')
|
||||
const feedbackArray = wrapper.findAll('div.invalid-feedback').at(1).findAll('span')
|
||||
expect(feedbackArray).toHaveLength(4)
|
||||
expect(feedbackArray.at(0).text()).toBe('site.signup.lowercase')
|
||||
})
|
||||
|
||||
it('removes first and second message when a lowercase character is given', async () => {
|
||||
await wrapper.findAll('input').at(1).setValue('a')
|
||||
await flushPromises()
|
||||
const feedbackArray = wrapper.findAll('div.invalid-feedback').at(1).findAll('span')
|
||||
expect(feedbackArray).toHaveLength(3)
|
||||
expect(feedbackArray.at(0).text()).toBe('site.signup.uppercase')
|
||||
})
|
||||
|
||||
it('removes the first three messages when a lowercase and uppercase characters are given', async () => {
|
||||
await wrapper.findAll('input').at(1).setValue('Aa')
|
||||
await flushPromises()
|
||||
const feedbackArray = wrapper.findAll('div.invalid-feedback').at(1).findAll('span')
|
||||
expect(feedbackArray).toHaveLength(2)
|
||||
expect(feedbackArray.at(0).text()).toBe('site.signup.one_number')
|
||||
})
|
||||
|
||||
it('removes the first four messages when a lowercase, uppercase and numeric characters are given', async () => {
|
||||
await wrapper.findAll('input').at(1).setValue('Aa1')
|
||||
await flushPromises()
|
||||
const feedbackArray = wrapper.findAll('div.invalid-feedback').at(1).findAll('span')
|
||||
expect(feedbackArray).toHaveLength(1)
|
||||
expect(feedbackArray.at(0).text()).toBe('site.signup.minimum')
|
||||
})
|
||||
|
||||
it('removes all messages when all rules are fulfilled', async () => {
|
||||
await wrapper.findAll('input').at(1).setValue('Aa123456')
|
||||
await flushPromises()
|
||||
const feedbackArray = wrapper.findAll('div.invalid-feedback').at(1).findAll('span')
|
||||
expect(feedbackArray).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('submit', () => {
|
||||
describe('valid data', () => {
|
||||
beforeEach(async () => {
|
||||
await form.findAll('input').at(0).setValue('1234')
|
||||
await form.findAll('input').at(1).setValue('Aa123456')
|
||||
await form.findAll('input').at(2).setValue('Aa123456')
|
||||
await form.trigger('submit')
|
||||
await flushPromises()
|
||||
})
|
||||
|
||||
it('calls the API', () => {
|
||||
expect(changePasswordProfileMock).toHaveBeenCalledWith(
|
||||
1,
|
||||
'user@example.org',
|
||||
'1234',
|
||||
'Aa123456',
|
||||
)
|
||||
})
|
||||
|
||||
it('toasts a success message', () => {
|
||||
expect(toastSuccessMock).toBeCalledWith('site.thx.reset')
|
||||
})
|
||||
|
||||
it('cancels the edit process', () => {
|
||||
expect(wrapper.find('input').exists()).toBeFalsy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('server response is error', () => {
|
||||
beforeEach(async () => {
|
||||
changePasswordProfileMock.mockReturnValue({
|
||||
success: false,
|
||||
result: { message: 'error' },
|
||||
})
|
||||
await form.findAll('input').at(0).setValue('1234')
|
||||
await form.findAll('input').at(1).setValue('Aa123456')
|
||||
await form.findAll('input').at(2).setValue('Aa123456')
|
||||
await form.trigger('submit')
|
||||
await flushPromises()
|
||||
})
|
||||
|
||||
it('toasts an error message', () => {
|
||||
expect(toastErrorMock).toBeCalledWith('error')
|
||||
})
|
||||
})
|
||||
})
|
||||
*/
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,217 +1,94 @@
|
||||
<template>
|
||||
<b-card id="change_pwd" class="bg-transparent" style="background-color: #ebebeba3 !important">
|
||||
<b-container>
|
||||
<b-form @keyup.prevent="loadSubmitButton">
|
||||
<div v-if="!editPassword">
|
||||
<b-row class="mb-4 text-right">
|
||||
<b-col class="text-right">
|
||||
<a href="#change_pwd" v-if="!editPassword" @click="editPassword = !editPassword">
|
||||
<a href="#change_pwd" @click="editPassword = !editPassword">
|
||||
<span>{{ $t('form.change-password') }}</span>
|
||||
<b-icon class="pointer ml-3" icon="pencil" />
|
||||
</a>
|
||||
|
||||
<b-icon
|
||||
v-else
|
||||
@click="cancelEdit()"
|
||||
class="pointer"
|
||||
icon="x-circle"
|
||||
variant="danger"
|
||||
></b-icon>
|
||||
</b-col>
|
||||
</b-row>
|
||||
|
||||
<div v-if="editPassword">
|
||||
<b-row class="mb-5">
|
||||
<b-col class="col-12 col-lg-3 col-md-10 col-sm-10 text-md-left text-lg-right">
|
||||
<small>{{ $t('form.password_old') }}</small>
|
||||
</b-col>
|
||||
<b-col class="col-md-9 col-sm-10">
|
||||
<b-input-group>
|
||||
<b-form-input
|
||||
class="mb-0"
|
||||
v-model="password"
|
||||
name="Password"
|
||||
:type="passwordVisibleOldPwd ? 'text' : 'password'"
|
||||
prepend-icon="ni ni-lock-circle-open"
|
||||
</div>
|
||||
<div v-if="editPassword">
|
||||
<b-row class="mb-4 text-right">
|
||||
<b-col class="text-right">
|
||||
<b-icon @click="cancelEdit()" class="pointer" icon="x-circle" variant="danger"></b-icon>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<validation-observer ref="observer" v-slot="{ handleSubmit }">
|
||||
<b-form @submit.stop.prevent="handleSubmit(onSubmit)">
|
||||
<b-row class="mb-2">
|
||||
<b-col>
|
||||
<input-password
|
||||
:label="$t('form.password_old')"
|
||||
:placeholder="$t('form.password_old')"
|
||||
></b-form-input>
|
||||
|
||||
<b-input-group-append>
|
||||
<b-button variant="outline-primary" @click="togglePasswordVisibilityOldPwd">
|
||||
<b-icon :icon="passwordVisibleOldPwd ? 'eye' : 'eye-slash'" />
|
||||
v-model="form.password"
|
||||
></input-password>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<input-password-confirmation v-model="form.newPassword" />
|
||||
<b-row class="text-right">
|
||||
<b-col>
|
||||
<div class="text-right">
|
||||
<b-button type="submit" variant="primary" class="mt-4">
|
||||
{{ $t('form.save') }}
|
||||
</b-button>
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
</b-col>
|
||||
</b-row>
|
||||
|
||||
<b-row class="mb-3">
|
||||
<b-col class="col-12 col-lg-3 col-md-10 col-sm-10 text-md-left text-lg-right">
|
||||
<small>{{ $t('form.password_new') }}</small>
|
||||
</b-col>
|
||||
<b-col class="col-md-9 col-sm-10">
|
||||
<b-input-group>
|
||||
<b-form-input
|
||||
class="mb-0"
|
||||
v-model="passwordNew"
|
||||
name="Password"
|
||||
:type="passwordVisibleNewPwd ? 'text' : 'password'"
|
||||
prepend-icon="ni ni-lock-circle-open"
|
||||
:placeholder="$t('form.password_new')"
|
||||
></b-form-input>
|
||||
|
||||
<b-input-group-append>
|
||||
<b-button variant="outline-primary" @click="togglePasswordVisibilityNewPwd">
|
||||
<b-icon :icon="passwordVisibleNewPwd ? 'eye' : 'eye-slash'" />
|
||||
</b-button>
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row class="mb-3">
|
||||
<b-col class="col-12 col-lg-3 col-md-10 col-sm-10 text-md-left text-lg-right">
|
||||
<small>{{ $t('form.password_new_repeat') }}</small>
|
||||
</b-col>
|
||||
<b-col class="col-md-9 col-sm-10">
|
||||
<b-input-group>
|
||||
<b-form-input
|
||||
class="mb-0"
|
||||
v-model="passwordNewRepeat"
|
||||
name="Password"
|
||||
:type="passwordVisibleNewPwdRepeat ? 'text' : 'password'"
|
||||
prepend-icon="ni ni-lock-circle-open"
|
||||
:placeholder="$t('form.password_new_repeat')"
|
||||
></b-form-input>
|
||||
|
||||
<b-input-group-append>
|
||||
<b-button variant="outline-primary" @click="togglePasswordVisibilityNewPwdRepeat">
|
||||
<b-icon :icon="passwordVisibleNewPwdRepeat ? 'eye' : 'eye-slash'" />
|
||||
</b-button>
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row>
|
||||
<b-col></b-col>
|
||||
<b-col class="col-12">
|
||||
<transition name="hint" appear>
|
||||
<div v-if="passwordValidation.errors.length > 0" class="hints">
|
||||
<ul>
|
||||
<li v-for="error in passwordValidation.errors" :key="error">
|
||||
<small>{{ error }}</small>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</transition>
|
||||
</b-col>
|
||||
</b-row>
|
||||
|
||||
<b-row class="text-right" v-if="editPassword">
|
||||
<b-col>
|
||||
<div class="text-right" ref="submitButton">
|
||||
<b-button
|
||||
:variant="loading ? 'default' : 'success'"
|
||||
@click="onSubmit"
|
||||
type="submit"
|
||||
class="mt-4"
|
||||
:disabled="loading"
|
||||
>
|
||||
{{ $t('form.save') }}
|
||||
</b-button>
|
||||
</div>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</div>
|
||||
</b-form>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-form>
|
||||
</validation-observer>
|
||||
</div>
|
||||
</b-container>
|
||||
</b-card>
|
||||
</template>
|
||||
<script>
|
||||
import loginAPI from '../../../apis/loginAPI'
|
||||
import InputPassword from '../../../components/Inputs/InputPassword'
|
||||
import InputPasswordConfirmation from '../../../components/Inputs/InputPasswordConfirmation'
|
||||
|
||||
export default {
|
||||
name: 'FormUserPasswort',
|
||||
components: {
|
||||
InputPassword,
|
||||
InputPasswordConfirmation,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
editPassword: false,
|
||||
email: null,
|
||||
password: '',
|
||||
passwordNew: '',
|
||||
passwordNewRepeat: '',
|
||||
passwordVisibleOldPwd: false,
|
||||
passwordVisibleNewPwd: false,
|
||||
passwordVisibleNewPwdRepeat: false,
|
||||
loading: true,
|
||||
form: {
|
||||
password: '',
|
||||
newPassword: {
|
||||
password: '',
|
||||
passwordRepeat: '',
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
cancelEdit() {
|
||||
this.editPassword = false
|
||||
this.password = ''
|
||||
this.passwordNew = ''
|
||||
this.passwordNewRepeat = ''
|
||||
this.form.password = ''
|
||||
this.form.passwordNew = ''
|
||||
this.form.passwordNewRepeat = ''
|
||||
},
|
||||
togglePasswordVisibilityNewPwd() {
|
||||
this.passwordVisibleNewPwd = !this.passwordVisibleNewPwd
|
||||
},
|
||||
togglePasswordVisibilityNewPwdRepeat() {
|
||||
this.passwordVisibleNewPwdRepeat = !this.passwordVisibleNewPwdRepeat
|
||||
},
|
||||
togglePasswordVisibilityOldPwd() {
|
||||
this.passwordVisibleOldPwd = !this.passwordVisibleOldPwd
|
||||
},
|
||||
loadSubmitButton() {
|
||||
if (
|
||||
this.password !== '' &&
|
||||
this.passwordNew !== '' &&
|
||||
this.passwordNewRepeat !== '' &&
|
||||
this.passwordNew === this.passwordNewRepeat
|
||||
) {
|
||||
this.loading = false
|
||||
} else {
|
||||
this.loading = true
|
||||
}
|
||||
},
|
||||
async onSubmit(event) {
|
||||
event.preventDefault()
|
||||
async onSubmit() {
|
||||
const result = await loginAPI.changePasswordProfile(
|
||||
this.$store.state.sessionId,
|
||||
this.$store.state.email,
|
||||
this.password,
|
||||
this.passwordNew,
|
||||
this.form.password,
|
||||
this.form.newPassword.password,
|
||||
)
|
||||
if (result.success) {
|
||||
this.$toast.success(this.$t('site.thx.reset'))
|
||||
this.$toasted.success(this.$t('site.thx.reset'))
|
||||
this.cancelEdit()
|
||||
} else {
|
||||
this.$toast.error(result.result.message)
|
||||
this.$toasted.error(result.result.message)
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
samePasswords() {
|
||||
return this.password === this.passwordNew
|
||||
},
|
||||
rules() {
|
||||
return [
|
||||
{ message: this.$t('site.signup.lowercase'), regex: /[a-z]+/ },
|
||||
{ message: this.$t('site.signup.uppercase'), regex: /[A-Z]+/ },
|
||||
{ message: this.$t('site.signup.minimum'), regex: /.{8,}/ },
|
||||
{ message: this.$t('site.signup.one_number'), regex: /[0-9]+/ },
|
||||
]
|
||||
},
|
||||
passwordValidation() {
|
||||
const errors = []
|
||||
for (const condition of this.rules) {
|
||||
if (!condition.regex.test(this.passwordNew)) {
|
||||
errors.push(condition.message)
|
||||
}
|
||||
}
|
||||
if (errors.length === 0) {
|
||||
return { valid: true, errors }
|
||||
}
|
||||
return { valid: false, errors }
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style></style>
|
||||
|
||||
@ -1,29 +1,28 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { extend } from 'vee-validate'
|
||||
import UserCardFormUsername from './UserCard_FormUsername'
|
||||
import loginAPI from '../../../apis/loginAPI'
|
||||
import flushPromises from 'flush-promises'
|
||||
import { extend } from 'vee-validate'
|
||||
|
||||
jest.mock('../../../apis/loginAPI')
|
||||
|
||||
extend('gddUsernameRgex', {
|
||||
validate(value) {
|
||||
return true
|
||||
},
|
||||
})
|
||||
|
||||
extend('gddUsernameUnique', {
|
||||
validate(value) {
|
||||
return true
|
||||
},
|
||||
})
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const mockAPIcall = jest.fn((args) => {
|
||||
return { success: true }
|
||||
})
|
||||
|
||||
// override this rule to avoid API call
|
||||
extend('gddUsernameUnique', {
|
||||
validate(value) {
|
||||
return true
|
||||
},
|
||||
})
|
||||
|
||||
const toastErrorMock = jest.fn()
|
||||
const toastSuccessMock = jest.fn()
|
||||
const storeCommitMock = jest.fn()
|
||||
|
||||
loginAPI.changeUsernameProfile = mockAPIcall
|
||||
|
||||
describe('UserCard_FormUsername', () => {
|
||||
@ -37,10 +36,11 @@ describe('UserCard_FormUsername', () => {
|
||||
email: 'user@example.org',
|
||||
username: '',
|
||||
},
|
||||
commit: jest.fn(),
|
||||
commit: storeCommitMock,
|
||||
},
|
||||
$toast: {
|
||||
success: jest.fn(),
|
||||
$toasted: {
|
||||
success: toastSuccessMock,
|
||||
error: toastErrorMock,
|
||||
},
|
||||
}
|
||||
|
||||
@ -111,10 +111,43 @@ describe('UserCard_FormUsername', () => {
|
||||
expect(wrapper.find('div.display-username').text()).toEqual('@username')
|
||||
})
|
||||
|
||||
it('commits the username to the store', () => {
|
||||
expect(storeCommitMock).toBeCalledWith('username', 'username')
|
||||
})
|
||||
|
||||
it('toasts an success message', () => {
|
||||
expect(toastSuccessMock).toBeCalledWith('site.profil.user-data.change-success')
|
||||
})
|
||||
|
||||
it('has no edit button anymore', () => {
|
||||
expect(wrapper.find('svg.bi-pencil').exists()).toBeFalsy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('submit retruns error', () => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
mockAPIcall.mockReturnValue({
|
||||
success: false,
|
||||
result: { message: 'Error' },
|
||||
})
|
||||
await wrapper.find('input[placeholder="Username"]').setValue('username')
|
||||
await wrapper.find('form').trigger('submit')
|
||||
await flushPromises()
|
||||
})
|
||||
|
||||
it('calls the loginAPI', () => {
|
||||
expect(mockAPIcall).toHaveBeenCalledWith(1, 'user@example.org', 'username')
|
||||
})
|
||||
|
||||
it('toasts an error message', () => {
|
||||
expect(toastErrorMock).toBeCalledWith('Error')
|
||||
})
|
||||
|
||||
it('renders an empty username', () => {
|
||||
expect(wrapper.find('div.display-username').text()).toEqual('@')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -95,9 +95,9 @@ export default {
|
||||
this.$store.commit('username', this.form.username)
|
||||
this.username = this.form.username
|
||||
this.showUsername = true
|
||||
this.$toast.success(this.$t('site.profil.user-data.change-success'))
|
||||
this.$toasted.success(this.$t('site.profil.user-data.change-success'))
|
||||
} else {
|
||||
this.$toast.error(result.result.message)
|
||||
this.$toasted.error(result.result.message)
|
||||
this.showUsername = true
|
||||
this.username = this.$store.state.username
|
||||
this.form.username = this.$store.state.username
|
||||
|
||||
41
frontend/src/views/Pages/UserProfileTransactionList.spec.js
Normal file
41
frontend/src/views/Pages/UserProfileTransactionList.spec.js
Normal file
@ -0,0 +1,41 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import UserProfileTransactionList from './UserProfileTransactionList'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
describe('UserProfileTransactionList', () => {
|
||||
let wrapper
|
||||
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
}
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(UserProfileTransactionList, { localVue, mocks })
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('renders the transaction table', () => {
|
||||
expect(wrapper.findComponent({ name: 'GddTransactionList' }).exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('emits update-transactions after creation', () => {
|
||||
expect(wrapper.emitted('update-transactions')).toEqual(
|
||||
expect.arrayContaining([expect.arrayContaining([{ firstPage: 1, items: 25 }])]),
|
||||
)
|
||||
})
|
||||
|
||||
it('emist update-transactions when update-transactions is called', () => {
|
||||
wrapper
|
||||
.findComponent({ name: 'GddTransactionList' })
|
||||
.vm.$emit('update-transactions', { firstPage: 2, items: 25 })
|
||||
expect(wrapper.emitted('update-transactions')).toEqual(
|
||||
expect.arrayContaining([expect.arrayContaining([{ firstPage: 2, items: 25 }])]),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -19,12 +19,13 @@
|
||||
import GddTransactionList from './AccountOverview/GddTransactionList.vue'
|
||||
|
||||
export default {
|
||||
name: 'UserProfileTransactionList',
|
||||
components: {
|
||||
GddTransactionList,
|
||||
},
|
||||
props: {
|
||||
transactions: {
|
||||
default: [],
|
||||
default: () => [],
|
||||
},
|
||||
transactionCount: { type: Number, default: 0 },
|
||||
},
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import { createLocalVue } from '@vue/test-utils'
|
||||
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
|
||||
import Vuex from 'vuex'
|
||||
|
||||
import { ValidationProvider, ValidationObserver, extend } from 'vee-validate'
|
||||
import * as rules from 'vee-validate/dist/rules'
|
||||
|
||||
import { messages } from 'vee-validate/dist/locale/en.json'
|
||||
|
||||
import RegeneratorRuntime from 'regenerator-runtime'
|
||||
import SideBar from '@/components/SidebarPlugin'
|
||||
import VueQrcode from 'vue-qrcode'
|
||||
@ -14,7 +15,7 @@ import VueMoment from 'vue-moment'
|
||||
import clickOutside from '@/directives/click-ouside.js'
|
||||
import { focus } from 'vue-focus'
|
||||
|
||||
global.localVue = createLocalVue()
|
||||
import { loadAllRules } from '../src/validation-rules'
|
||||
|
||||
Object.keys(rules).forEach((rule) => {
|
||||
extend(rule, {
|
||||
@ -23,6 +24,15 @@ Object.keys(rules).forEach((rule) => {
|
||||
})
|
||||
})
|
||||
|
||||
const i18nMock = {
|
||||
t: (identifier, values) => identifier,
|
||||
n: (value, format) => value,
|
||||
}
|
||||
|
||||
loadAllRules(i18nMock)
|
||||
|
||||
global.localVue = createLocalVue()
|
||||
|
||||
global.localVue.use(BootstrapVue)
|
||||
global.localVue.use(Vuex)
|
||||
global.localVue.use(IconsPlugin)
|
||||
|
||||
@ -13268,11 +13268,6 @@ vm-browserify@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
|
||||
integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
|
||||
|
||||
vue-bootstrap-toasts@^1.0.7:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/vue-bootstrap-toasts/-/vue-bootstrap-toasts-1.0.7.tgz#111c38855941e8eb0538e21f41c173e2af67dd53"
|
||||
integrity sha512-JhurJOAwdNcINQ/QlT701sx0r447YTGpvtxtmZNC9pwDvEqp2I0Pyv15jS4neWwYHkA1gXB42nBsDRcWcj1hlg==
|
||||
|
||||
vue-bootstrap-typeahead@^0.2.6:
|
||||
version "0.2.6"
|
||||
resolved "https://registry.yarnpkg.com/vue-bootstrap-typeahead/-/vue-bootstrap-typeahead-0.2.6.tgz#8c1999a00bf4bf9fc906bae3a462482482cbc297"
|
||||
@ -13475,6 +13470,11 @@ vue-template-es2015-compiler@^1.6.0, vue-template-es2015-compiler@^1.9.0:
|
||||
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
|
||||
integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
|
||||
|
||||
vue-toasted@^1.1.28:
|
||||
version "1.1.28"
|
||||
resolved "https://registry.yarnpkg.com/vue-toasted/-/vue-toasted-1.1.28.tgz#dbabb83acc89f7a9e8765815e491d79f0dc65c26"
|
||||
integrity sha512-UUzr5LX51UbbiROSGZ49GOgSzFxaMHK6L00JV8fir/CYNJCpIIvNZ5YmS4Qc8Y2+Z/4VVYRpeQL2UO0G800Raw==
|
||||
|
||||
vue2-transitions@^0.2.3:
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/vue2-transitions/-/vue2-transitions-0.2.3.tgz#69c9d75b1db05f231b80980c03459d68490ba27d"
|
||||
|
||||
@ -123,13 +123,18 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params)
|
||||
}
|
||||
}
|
||||
else if ("User.description" == name) {
|
||||
std::string str_val = validateString(value, "User.description", jsonErrorsArray);
|
||||
std::string errorMessage = "User.description";
|
||||
|
||||
if (str_val.size() > 0 && str_val != user_model->getDescription()) {
|
||||
if (!value.isString()) {
|
||||
errorMessage += " isn't a string";
|
||||
jsonErrorsArray.add(errorMessage);
|
||||
}
|
||||
std::string str_val = value.toString();
|
||||
|
||||
if (str_val != user_model->getDescription()) {
|
||||
user_model->setDescription(str_val);
|
||||
extractet_values++;
|
||||
}
|
||||
|
||||
}
|
||||
else if ("User.disabled" == name) {
|
||||
bool disabled;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user