, as the view writes it. if (!preg_match('//s', $view, $m)) die("could not find the actions table's thead in the view\n"); $thead = $m[0]; // Each header is translate('Key'), so the keys are the column names. preg_match_all("/.*?<\/tr>/s', $view, $m)) die("could not find an action row in the view\n"); $row = $m[0]; check('and a body row has a cell for each', preg_match_all('/]/', $row), count($columns)); echo "the css pins the columns it says it does\n"; // Which positions the css gives a width to, and what width. preg_match_all( '/#monitorActionsTable th:nth-child\((\d+)\),\s*'. '#monitorActionsTable td:nth-child\(\d+\)\s*\{([^}]*)\}/s', $css, $m, PREG_SET_ORDER); $pinned = array(); foreach ($m as $rule) { if (preg_match('/width:\s*([^;]+);/', $rule[2], $w)) $pinned[(int)$rule[1]] = trim($w[1]); } ksort($pinned); check('four columns are pinned', count($pinned), 4); // The point of the exercise: each pin is on the column it was written for. // Read out of the view by position rather than restated, so a reordering // fails here instead of going unnoticed. function column_at($columns, $n) { return isset($columns[$n - 1]) ? $columns[$n - 1] : null; } $pinned_columns = array(); foreach ($pinned as $n => $width) $pinned_columns[column_at($columns, $n)] = $width; check('and they are Enabled, When, Schedule and Sound', array_keys($pinned_columns), array('Enabled', 'ActionTrigger', 'ActionSchedule', 'ActionAudioFile')); // Device is left loose on purpose: monitor names are the only contents here // of unpredictable length, so that is the column that should absorb whatever // the pinned ones do not take. check('and Device is not among them', in_array('ActionTarget', array_keys($pinned_columns), true), false); echo "the widths suit what is in the columns\n"; // Not exact values -- those are a judgement and may be tuned -- but the // ordering between them, which is the thing that would be wrong if a pin // moved: the sentence needs more room than the checkbox or the number. $ems = array(); foreach ($pinned_columns as $name => $width) { check($name.' is pinned in em', (bool)preg_match('/^([\d.]+)em$/', $width, $w), true); $ems[$name] = isset($w[1]) ? (float)$w[1] : 0; } check('Schedule is the widest of the three', ($ems['ActionSchedule'] > $ems['Enabled']) and ($ems['ActionSchedule'] > $ems['ActionAudioFile']), true); // The other two are the narrow ones. No ordering is asserted between them: // under a fixed layout a column has to fit its header, which skin.css renders // uppercase, and "ENABLED" is a wider word than "SOUND" whatever the cells // below them hold. An earlier version of this test had Enabled as the // narrowest "being one checkbox", which is the reasoning that got it set too // narrow to clear its own header in the first place. check('and the other two are narrow by comparison', ($ems['Enabled'] < $ems['ActionSchedule'] / 2) and ($ems['ActionAudioFile'] < $ems['ActionSchedule'] / 2), true); // A column narrower than its header runs its text into the next column, which // is how Enabled came to sit against When. The estimate below is crude on // purpose: it is here to catch a width chosen from what the cells hold while // forgetting the header above them, not to predict text metrics. // // Calibrated so that the 4em Enabled started out with fails it and the 5.5em // it has now passes. The header text is taken from the language file rather // than written out here, so a translation long enough to break the layout // fails this too -- under a fixed layout it really would break it. $SLANG = array(); require_once($root.'/web/lang/en_gb.php'); // skin.css renders every th uppercase at 0.8em, so that is what has to fit, // along with .table-sm's 0.3rem of padding on each side. $th_scale = 0.8; $per_char = 0.65; // an average uppercase advance, in the header's own em $padding = 0.6; foreach (array_keys($pinned_columns) as $name) { $text = isset($SLANG[$name]) ? $SLANG[$name] : $name; // strlen rather than mb_strlen: this php has no mbstring, and counting // bytes only over-counts a multibyte translation, which errs towards // demanding more width rather than less. $needed = (strlen($text) * $per_char * $th_scale) + $padding; check($name.' ('.strtoupper($text).') is wider than its header needs', $ems[$name] > $needed, true); } echo "the two select columns fit their longest option\n"; // What actually sets these two widths is not the header but the widest //
<\?php echo translate\('([A-Za-z]+)'\) \?><\/th>/", $thead, $m); $columns = $m[1]; echo "the view's columns are the ones the css was written against\n"; check('six columns', count($columns), 6); // Named, so that a reordering shows up here as a diff rather than as a silent // change of which column is narrow. check('in this order', $columns, array( 'Enabled', // 1: a checkbox 'ActionTrigger', // 2: When 'ActionSchedule', // 3: Every at
\s*li\s*\{[^}]*padding-left:\s*300px/s', $css), true); check('and pulls the label back into that gutter', (bool)preg_match('/ul\.form\s*>\s*li\s*>\s*label:first-child\s*\{[^}]*margin-left:\s*-300px/s', $css), true); preg_match('/ul\.form\s*>\s*li\.([\w-]+)\s*\{[^}]*padding-left:\s*0/s', $css, $m); $no_label_class = isset($m[1]) ? $m[1] : null; check('a class cancels the indent', $no_label_class !== null, true); if ($no_label_class !== null) { // The row holding the table must carry it, or the table is still indented. check('and the table\'s row carries it', (bool)preg_match('/
  • \s*'. '
  • = 4, true); // Every row the class is on must genuinely have no label, otherwise the // label loses the gutter it is pulled into and hangs off the left edge. preg_match_all('/
  • (.*?)<\/li>/s', $view, $m); $with_label = array(); foreach ($m[1] as $body) { if (preg_match('/