znt/module/company/test/lib/ui/dynamic.ui.class.php

395 lines
16 KiB
PHP
Raw Permalink Normal View History

<?php
require_once dirname(__FILE__, 6) . '/test/lib/ui.php';
const DYNAMIC_MENUS = ['all', 'today', 'yesterday', 'thisWeek', 'lastWeek', 'thisMonth', 'lastMonth'];
class dynamicTester extends tester
{
public $users;
public $tasks;
public $groupPrivs;
public $actions;
public $actionIndex;
public $actionsByKey;
public $pageTitle;
public $prodNames;
public $projNames;
public $execNames;
public $userNames;
public function __construct()
{
parent::__construct();
global $uiTester;
$users = $uiTester->dao->select('u.*, ug.`group`')
->from(TABLE_USER)->alias('u')
->leftJoin(TABLE_USERGROUP)->alias('ug')->on('u.account = ug.account')
->fetchAll();
$groupPrivs = $uiTester->dao->select('*')->from(TABLE_GROUPPRIV)->fetchAll();
$this->tasks = $uiTester->dao->select('id,name')->from(TABLE_TASK)->orderBy('id')->fetchAll();
$this->prodNames = array_keys($uiTester->dao->select('name')->from(TABLE_PRODUCT)->fetchAll('name'));
$this->projNames = array_keys($uiTester->dao->select('name')->from(TABLE_PROJECT)->where('type')->eq('project')->fetchAll('name'));
$this->execNames = array_keys($uiTester->dao->select('name')->from(TABLE_PROJECT)->where('type')->eq('sprint')->fetchAll('name'));
$this->groupPrivs = [];
$this->actions = [];
$this->pageTitle = $this->lang->company->dynamic ?? '动态';
$this->userNames = ['all']; //'all'是一个虚拟的用户名,代表所有用户
foreach($users as $u)
{
$this->users[$u->realname] = $u;
$this->userNames[] = $u->realname;
}
foreach($groupPrivs as $gp) $this->groupPrivs[$gp->group][$gp->module][$gp->method] = $gp;
}
/**
* 用户登录后从数据库中刷新数据(因为增加了登录信息)
* Refresh the dynamic data after login
*
*/
private function refreshDataFromDB()
{
global $uiTester;
$this->actions['all'] = [];
foreach($this->users as $user)
{
$action = $uiTester->dao->select('a.*, user.realname as userName, prod.name as prodName, proj.name as projName, exec.name as execName')
->from(TABLE_ACTION)->alias('a')
->leftJoin(TABLE_ACTIONPRODUCT)->alias('ap')->on('a.id = ap.action')
->leftJoin(TABLE_USER)->alias('user')->on('a.actor = user.account')
->leftJoin(TABLE_PRODUCT)->alias('prod')->on('ap.product = prod.id')
->leftJoin(TABLE_PROJECT)->alias('proj')->on('a.project = proj.id')
->leftJoin(TABLE_PROJECT)->alias('exec')->on('a.execution = exec.id')
->where('a.actor')->eq($user->account)
->fetchAll();
$this->actions[$user->realname] = $action;
$this->actions['all'] = array_merge($this->actions['all'], $action);
}
$this->buildActionIndex();
// 预构建所有维度组合的键值映射,使用笛卡尔积生成器替代多层嵌套循环。(效率一样,只是代码更简洁)
$this->actionsByKey = [];
foreach($this->cartesian([DYNAMIC_MENUS, $this->userNames, $this->prodNames, $this->projNames, $this->execNames]) as $combo)
{
list($menu, $userName, $prodName, $projName, $execName) = $combo;
$key = "{$menu}|{$userName}|{$prodName}|{$projName}|{$execName}";
$this->actionsByKey[$key] = $this->getActionsByDims($menu, $userName, $prodName, $projName, $execName);
}
}
/**
* 校验company模块dynamic视图内容
* Verify the content of company dynamic view.
*
* @return object 成功或失败对象
* @access public
*/
public function verifyCompanyDynamicContent()
{
foreach($this->users as $user)
{
$this->login($user->account);
$hasGroupPriv = ($user->account == 'admin') || isset($this->groupPrivs[$user->group]['company']['dynamic']);
if($hasGroupPriv)
{
$this->refreshDataFromDB();
$form = $this->initForm('company', 'dynamic', [], 'appIframe-system');
$form->wait(2);
$ret = $this->verifySelectedContent($form, $user);
if($ret) return $ret;
$ret = $this->verifySearch($form);
if($ret) return $ret;
}
else
{
$form = null;
try
{
$form = $this->initForm('company', 'dynamic', [], 'appIframe-system');
return $this->failed("用户'{$user->account}'无'{$this->pageTitle}'页面访问权限却能访问页面");
}
catch(Exception $e)
{
// 如果页面对象已初始化,则读取拒绝信息;否则视为无法访问直接跳过
if(is_object($form))
{
$form->wait(2);
$msg = $form->dom->denied->getText();
// 用户没有team权限应该显示拒绝框
if($msg == $user->account . " " . $this->lang->user->deny) continue;
return $this->failed("用户'{$user->account}'无'{$this->pageTitle}'页面访问权限却未显示拒绝框");
}
continue;
}
}
}
return $this->success('开源版m=company&f=dynamic测试成功');
}
/**
* 抽查给定索引的任务动态搜索结果
* Verify the search result of company dynamic view.
*
* @param object $form 页面对象
* @param int $index 任务索引,默认-1表示最后一个任务
* @return object 成功或失败对象
* @access public
*/
private function verifySearch($form, $index = -1)
{
// 抽查ID=60的动态是否有数据
$search = [
'field1' => $this->lang->company->product,
'value1' => $this->lang->product->allProduct,
'field4' => $this->lang->action->objectID];
$form->dom->searchForm->click();
$form->wait(1);
foreach($search as $key => $value)
{
$form->dom->{$key}->picker($value);
$form->wait(1);
}
$index = $index < 0 ? count($this->tasks) - 1 : $index;
$form->dom->value4->setValue($this->tasks[$index]->id);
$form->wait(1);
$form->dom->searchBtn->click();
$form->wait(2);
$displayed = $form->dom->searchResult->getText();
$expected = $this->tasks[$index]->name;
if($displayed != $expected) return $this->failed("ID={$this->tasks[$index]->id}的任务动态搜索结果为{$displayed},不是预期的任务{$expected}");
return null;
}
/**
* 根据下拉菜单的选择校验内容
* Verify content based on dropdown menu selection
*
* @param object $form 页面对象
* @param object $user 当前登录用户
* @return object 成功返回null或失败返回对象
*/
private function verifySelectedContent($form, $user)
{
foreach(DYNAMIC_MENUS as $menu)
{
$header = $this->lang->company->featureBar->dynamic->{$menu} ?? $menu;
$form->dom->{$menu}->click();
foreach($this->userNames as $userName)
{
// 先设置筛选器,再点击周期菜单,确保计数依据最新条件重新渲染。
// 筛选菜单共4个用户|产品|项目|执行
// 先清除已选用户,再选择当前用户。
if($form->dom->userDeselect) $form->dom->userDeselect->click();
// 如果当前用户是'all则不选择任何用户。
if($userName != 'all' && $form->dom->userSelect->getText() != $userName) $form->dom->userSelect->picker($userName);
foreach($this->prodNames as $prodName)
{
if($form->dom->productSelect->getText() != $prodName) $form->dom->productSelect->picker($prodName);
foreach($this->projNames as $projName)
{
if($form->dom->projectSelect->getText() != $projName) $form->dom->projectSelect->picker($projName);
foreach($this->execNames as $execName)
{
// 具体执行名字前面有'/', 没有选择(即所有执行)不带'/'
$prefix = ($execName == $this->lang->execution->common) ? '' : '/';
if($form->dom->execSelect->getText() != $prefix . $execName) $form->dom->execSelect->picker($prefix . $execName);
$form->wait(1);
// 点击时间周期菜单刷新计数
$form->dom->{$menu}->click();
$form->wait(2);
// 读取计数元素前做存在性检查,避免渲染延迟导致空节点。
$countNode = $form->dom->{$menu . 'Count'};
if(!$countNode)
{
$form->wait(1);
$countNode = $form->dom->{$menu . 'Count'};
if(!$countNode) return $this->failed("菜单'{$header}'计数元素未渲染,无法读取数量");
}
$displayed = (int) trim($countNode->getText());
$key = "{$menu}|{$userName}|{$prodName}|{$projName}|{$execName}";
$expected = count($this->actionsByKey[$key] ?? []);
$userName = $userName == 'all' ? '所有用户' : $userName;
if($displayed != $expected) return $this->failed("用户'{$user->realname}'的'|{$header}|{$userName}|{$prodName}|{$projName}|{$execName}|' 动态数量错误,期望{$expected}条,显示{$displayed}");
}
}
}
}
}
return null;
}
/**
* 按照用户|产品|项目|执行构建操作索引
* Build action index based on user|product|project|execution.
*/
private function buildActionIndex()
{
$this->actionIndex = [];
foreach($this->actions as $account => $list)
{
if($account === 'all') continue;
if(empty($list)) continue;
foreach($list as $action)
{
$prod = trim(is_object($action) ? ($action->prodName ?? '') : ($action['prodName'] ?? ''));
$proj = trim(is_object($action) ? ($action->projName ?? '') : ($action['projName'] ?? ''));
$exec = trim(is_object($action) ? ($action->execName ?? '') : ($action['execName'] ?? ''));
$accountKey = trim($account);
$base = [$accountKey, $prod, $proj, $exec];
for($mask = 0; $mask < 16; $mask++)
{
$u = ($mask & 1) ? '__ALL__' : $base[0];
$p = ($mask & 2) ? '__ALL__' : $base[1];
$pr = ($mask & 4) ? '__ALL__' : $base[2];
$e = ($mask & 8) ? '__ALL__' : $base[3];
$k = $u . '|' . $p . '|' . $pr . '|' . $e;
if(!isset($this->actionIndex[$k])) $this->actionIndex[$k] = [];
$this->actionIndex[$k][] = $action;
}
}
}
// 显式添加汇总键,确保'全部'维度必有数据
$aggKey = '__ALL__|__ALL__|__ALL__|__ALL__';
$this->actionIndex[$aggKey] = $this->actions['all'] ?? [];
}
/**
* 根据给定维度组合返回数据
* Get actions based on the combination of given dimensions.
*
* @param string $menu 时间菜单
* @param string $userName 维度-用户("all"'realname'
* @param string $prodName 维度-产品名
* @param string $projName 维度-项目名
* @param string $execName 维度-执行名
* @return object|null 返回过滤后的列表
*/
public function getActionsByDims($menu, $userKey, $prodName, $projName, $execName)
{
// 将占位标签或“全部”视为 all 维度
$productLabel = $this->lang->company->product ?? '产品';
$projectLabel = $this->lang->company->project ?? '项目';
$execLabel = $this->lang->execution->common ?? '执行';
$allLabel = $this->lang->all ?? '全部';
$prodName = (in_array($prodName, [$productLabel, $allLabel, ''], true)) ? 'all' : trim($prodName);
$projName = (in_array($projName, [$projectLabel, $allLabel, ''], true)) ? 'all' : trim($projName);
$execName = (in_array($execName, [$execLabel, $allLabel, ''], true)) ? 'all' : trim($execName);
$userKey = ($userKey === 'all') ? 'all' : trim($userKey);
$u = ($userKey === 'all') ? '__ALL__' : $userKey;
$p = ($prodName === 'all') ? '__ALL__' : $prodName;
$pr = ($projName === 'all') ? '__ALL__' : $projName;
$e = ($execName === 'all') ? '__ALL__' : $execName;
$k = $u . '|' . $p . '|' . $pr . '|' . $e;
$list = $this->actionIndex[$k] ?? [];
return $this->filterItemsByMenu($list, $menu);
}
/**
* 根据时间菜单过滤动态列表。
* Filter dynamic by given period menu.
*
* @param array $actions 动态列表
* @param string $menu 菜单
* @return array 过滤后的动态列表
*/
private function filterItemsByMenu($actions, $menu)
{
if(empty($actions)) return [];
list($beginTS, $endTS) = $this->getBounds($menu);
if($beginTS === null || $endTS === null) return $actions;
$filtered = [];
foreach($actions as $id => $action)
{
$dateStr = is_object($action) ? ($action->date ?? '') : ($action['date'] ?? '');
if(empty($dateStr)) continue;
$ts = strtotime($dateStr);
if($ts !== false && $ts >= $beginTS && $ts < $endTS) $filtered[$id] = $action;
}
return $filtered;
}
/**
* 计算菜单的日期边界,返回 [beginTS, endTS],左闭右开。
* Calculate the date bounds for a given menu period.
*
* @param string $menu 菜单名称
* @return array 日期边界数组 [beginTS, endTS]
*/
private function getBounds($menu)
{
$key = strtolower($menu);
switch($key)
{
case 'today':
return [strtotime('today midnight'), strtotime('tomorrow')];
case 'yesterday':
return [strtotime('yesterday midnight'), strtotime('today midnight')];
case 'thisweek':
return [strtotime('monday this week'), strtotime('monday next week')];
case 'lastweek':
return [strtotime('monday last week'), strtotime('monday this week')];
case 'thismonth':
$begin = strtotime(date('Y-m-01'));
$end = strtotime(date('Y-m-01', strtotime('+1 month', $begin)));
return [$begin, $end];
case 'lastmonth':
$end = strtotime(date('Y-m-01'));
$begin = strtotime(date('Y-m-01', strtotime('-1 month', $end)));
return [$begin, $end];
default:
return [null, null];
}
}
/**
* 笛卡尔生成器:按顺序产出各集合的组合,避免多层嵌套循环。
* Cartesian generator producing combinations across provided sets.
*
* @param array $sets [A1, A2, ...],每个元素为一个可遍历集合
* @return Generator 依次yield形如 [a1, a2, ...] 的组合
*/
private function cartesian(array $sets)
{
if(empty($sets)) { yield []; return; }
$first = array_shift($sets);
foreach($first as $value)
{
// 递归yield后续集合的组合
foreach($this->cartesian($sets) as $comb)
{
yield array_merge([$value], $comb);
}
}
}
}