项目 7 情景控制:虚拟时钟、项目资源、Windows Media Player、TrackBar
- 虚拟时钟:实训台时钟面板可拨时间、1×/10×/60× 倍速、恢复真实时间;编译时把 DateTime.Now / Today / UtcNow 改写成 SimClock;评分规则加 clock 步骤 - 项目资源(Properties.Resources):平台素材库(自绘开关图片、合成门铃声),"项目资源"对话框勾选, 平台生成 Properties/Resources.Designer.cs;设计器图片框的"图片"属性可选资源;程序目录下的 Resources 指向素材库;未添加资源时给中文提示 - Windows Media Player(AxWMPLib):设置 URL 自动播放、Ctlcontrols.play/stop,浏览器里播放声音, 实训台加广播喇叭;编译时给窗体嵌空资源,OcxState 取到 null 不报错;图片和声音统一按内容缓存(blob) - TrackBar:拖动、点轨道、方向键,先 ValueChanged 再 Scroll;设计器加"滑动条""媒体播放器" - 评分规则加 value、media 检查;示例项目 7 任务 1–3(修正书上开关灯判断写反、漏填串口列表) - 修正:Properties/ 下的资源类不当作窗体;带命名空间的 System.AppDomain.CurrentDomain.BaseDirectory 放行 - 测试:虚拟时钟、TrackBar、项目 7 示例评分、作业起点带资源类 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a9af26c2d6
commit
eedf1aa2fb
66 files changed
+3538
-43
No files matched your search
@@ -35,7 +35,7 @@ async function prepare(id: string, name: string, edit?: { file: string; from: st
|
||||
|
||||
describe('参考答案全部通过', async () => {
|
||||
const ids = (await Array.fromAsync(new Bun.Glob('*/checks.yaml').scan({ cwd: SAMPLES }))).map((p) => p.split('/')[0]).sort()
|
||||
test('每个示例都有评分规则', () => expect(ids.length).toBe(15))
|
||||
test('每个示例都有评分规则', () => expect(ids.length).toBe(18))
|
||||
for (const id of ids) {
|
||||
test(id, async () => {
|
||||
const result = await grade(await prepare(id, id), await checksOf(id), id)
|
||||
@@ -110,6 +110,22 @@ describe('改坏的代码', () => {
|
||||
expect(result.results[0].message).toStartWith('程序出错:System.ArgumentNullException')
|
||||
}, 60000)
|
||||
|
||||
test('情景控制:照书上把手动开关灯的判断写反,点了灯不亮', async () => {
|
||||
const id = 'p7-t1-智慧教室'
|
||||
const dir = await prepare(id, 'lamp-reversed', { file: '教室情景模式.cs', from: ' if (IsLampOpen)\n', to: ' if (!IsLampOpen)\n' })
|
||||
const checks = [
|
||||
...(await checksOf(id)),
|
||||
...parseChecks(`
|
||||
- title: 能手动开关灯
|
||||
steps:
|
||||
- click: pic_lightsStatus
|
||||
- expect: { control: lamp, on: true, desc: 点开关后灯亮 }
|
||||
`),
|
||||
]
|
||||
const result = await grade(dir, checks, id)
|
||||
expect(result.results.at(-1)).toMatchObject({ passed: false, message: '期望窗体上的 lamp 点开关后灯亮,实际是灭的' })
|
||||
}, 60000)
|
||||
|
||||
test('关闭时忘了挂起线程', async () => {
|
||||
const id = 'p5-t2-烟雾'
|
||||
const dir = await prepare(id, 'thread', { file: 'Form1.cs', from: 'thd.Suspend();//挂起线程', to: '' })
|
||||
|
||||
@@ -4,6 +4,7 @@ import { afterAll, afterEach, describe, expect, test } from 'bun:test'
|
||||
import { cp, mkdir, rm } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
import type { ControlNode, RunnerInput, RunnerMessage } from '@iot/shared'
|
||||
import { wallNow } from '../src/board'
|
||||
import { compileService } from '../src/compile-service'
|
||||
import { config, ROOT } from '../src/config'
|
||||
import { cpuLimitMessage, runCommand } from '../src/sandbox'
|
||||
@@ -332,7 +333,7 @@ test('项目4 任务2:打开摄像机显示画面,转动后画面变化,
|
||||
const center = imageOf(await first)
|
||||
expect(center).toMatchObject({ width: 640, height: 480 })
|
||||
// 图片数据先于(或随)属性变化发给浏览器,同一张图只发一次
|
||||
const sent = runner.messages.filter((m) => m.type === 'image' && m.key === center.key)
|
||||
const sent = runner.messages.filter((m) => m.type === 'blob' && m.key === center.key)
|
||||
expect(sent.length).toBe(1)
|
||||
expect(sent[0]).toMatchObject({ mime: 'image/jpeg' })
|
||||
|
||||
@@ -630,6 +631,56 @@ test('数据库:照书上的写法建库建表、插入、查询、SqlCommandB
|
||||
}
|
||||
}, 30000)
|
||||
|
||||
test('虚拟时钟:拨时间、倍速后 DateTime.Now / Today 跟着走,恢复后是真实时间', async () => {
|
||||
const { runner } = await runMain(
|
||||
'clock',
|
||||
`
|
||||
for (int i = 0; i < 40; i++)
|
||||
{
|
||||
Console.WriteLine("now=" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " today=" + DateTime.Today.ToString("yyyy-MM-dd"));
|
||||
System.Threading.Thread.Sleep(100);
|
||||
}`,
|
||||
)
|
||||
expect((await runner.waitFor('compiled')).ok).toBe(true)
|
||||
await runner.waitFor('console')
|
||||
runner.send({ type: 'board', analog: ANALOG_DEFAULT, digital: DIGITAL_OFF, clock: { wall: '2024-01-16T06:44:58', realAt: Date.now(), speed: 60 } })
|
||||
// 60 倍速:真实的 1 秒是虚拟的 1 分钟
|
||||
await runner.next('console', (m) => /now=2024-01-16 06:4[6-9]/.test(m.text), 5000)
|
||||
runner.send({ type: 'board', analog: ANALOG_DEFAULT, digital: DIGITAL_OFF, clock: null })
|
||||
const real = await runner.next('console', (m) => !m.text.includes('2024-01-16'), 3000)
|
||||
// 学生程序按服务器配置的时区(config.timeZone)计时,CI 机器本身是 UTC
|
||||
expect(real.text).toContain(`today=${wallNow().slice(0, 10)}`)
|
||||
expect(runner.messages.some((m) => m.type === 'console' && m.text.includes('today=2024-01-16'))).toBe(true)
|
||||
}, 30000)
|
||||
|
||||
test('TrackBar:拖动时先 ValueChanged 再 Scroll,超出范围的值截到两端;代码设超出范围的 Value 报错', async () => {
|
||||
const { runner } = await runMain(
|
||||
'trackbar',
|
||||
`
|
||||
var form = new Form();
|
||||
var bar = new TrackBar { Name = "bar", Minimum = 18, Maximum = 32 };
|
||||
bar.ValueChanged += (s, e) => Console.WriteLine("changed=" + bar.Value);
|
||||
bar.Scroll += (s, e) => Console.WriteLine("scroll=" + bar.Value);
|
||||
form.Load += (s, e) =>
|
||||
{
|
||||
try { bar.Value = 40; } catch (ArgumentOutOfRangeException ex) { Console.WriteLine("error=" + ex.Message); }
|
||||
};
|
||||
form.Controls.Add(bar);
|
||||
Application.Run(form);`,
|
||||
'using System.Windows.Forms;',
|
||||
)
|
||||
const { form } = await runner.waitFor('formShown')
|
||||
const bar = findByName(form, 'bar')
|
||||
expect(bar.props).toMatchObject({ Minimum: 18, Maximum: 32, Value: 18 })
|
||||
runner.send({ type: 'action', id: bar.id, action: 'scroll', value: '26' })
|
||||
await runner.waitFor('console', (m) => m.text === 'scroll=26\n')
|
||||
runner.send({ type: 'action', id: bar.id, action: 'scroll', value: '99' })
|
||||
await runner.waitFor('console', (m) => m.text === 'scroll=32\n')
|
||||
const output = runner.messages.flatMap((m) => (m.type === 'console' ? [m.text] : [])).join('')
|
||||
expect(output).toContain("error=“40”的值对于“Value”无效。")
|
||||
expect(output).toContain('changed=26\nscroll=26\nchanged=32\nscroll=32\n')
|
||||
}, 30000)
|
||||
|
||||
test.skipIf(config.sandbox === 'none')('沙箱:看不到服务器文件、别的运行目录、环境变量和其他进程,工程目录只读', async () => {
|
||||
process.env.SIM_TEST_SECRET = '不该被看到'
|
||||
const { runner } = await runMain(
|
||||
|
||||
Reference in new issue
Block a user